gabriel / musehub public
seed_musehub.py python
4038 lines 232.8 KB
04faf0e3 feat: supercharge all repo pages, enforce separation of concerns Gabriel Cardona <cgcardona@gmail.com> 5d ago
1 """MuseHub stress-test seed script.
2
3 Inserts a rich, realistic dataset that exercises every implemented URL,
4 every sidebar section, every social feature, every analytics panel, and
5 every discovery/explore surface.
6
7 Scale:
8 - 8 community users (gabriel, sofia, marcus, yuki, aaliya, chen, fatou, pierre)
9 - 15 historical/licensed composers:
10 Public Domain: Bach, Chopin, Scott Joplin, Beethoven, Mozart, Debussy, Satie, Schubert
11 CC BY 4.0: Kevin MacLeod, Kai Engel
12 - 40+ repos:
13 Original community projects (8), fork repos (2)
14 Genre archive batch-13 (12): WTC, Goldberg, Nocturnes, Maple Leaf, etc.
15 Archive expansion batch-14 (6): Moonlight, Piano Sonatas, K.331, Suite Bergamasque,
16 Gymnopédies, Impromptus (sourced from Mutopia Project & piano-midi.de)
17 Cross-composer remixes batch-15 (5): Well-Tempered Rag, Nocturne Variations,
18 Moonlight Jazz, Clair de Lune Deconstructed, Ragtime Afrobeat
19 - 18-70 commits per repo (with realistic branch history)
20 - 10+ issues per repo, mix of open/closed
21 - 4+ PRs per repo (open, merged, closed)
22 - 2-4 releases per repo
23 - 3-8 sessions per repo
24 - Comments, reactions, follows, watches, notifications, forks, view events
25 - Commit objects (tracks) with real instrument roles for the breakdown bar
26 - Muse VCS layer: content-addressed MIDI objects, snapshots, commits, tags
27
28 Run inside the container:
29 docker compose exec musehub python3 /app/scripts/seed_musehub.py
30
31 Idempotent: pass --force to wipe and re-insert.
32 """
33 from __future__ import annotations
34
35 import asyncio
36 import hashlib
37 import json
38 import sys
39 import uuid
40 from datetime import datetime, timedelta, timezone
41 from typing import Any
42
43 from sqlalchemy import text
44 from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
45 from sqlalchemy.orm import sessionmaker
46
47 from musehub.config import settings
48 from musehub.db.models import (
49 AccessToken,
50 User,
51 )
52 from musehub.db.musehub_collaborator_models import MusehubCollaborator
53 from musehub.db.musehub_label_models import (
54 MusehubIssueLabel,
55 MusehubLabel,
56 MusehubPRLabel,
57 )
58 from musehub.db.musehub_models import (
59 MusehubBranch,
60 MusehubComment,
61 MusehubCommit,
62 MusehubDownloadEvent,
63 MusehubEvent,
64 MusehubFollow,
65 MusehubFork,
66 MusehubIssue,
67 MusehubIssueComment,
68 MusehubIssueMilestone,
69 MusehubMilestone,
70 MusehubNotification,
71 MusehubObject,
72 MusehubPRComment,
73 MusehubPRReview,
74 MusehubProfile,
75 MusehubPullRequest,
76 MusehubReaction,
77 MusehubRelease,
78 MusehubReleaseAsset,
79 MusehubRenderJob,
80 MusehubRepo,
81 MusehubSession,
82 MusehubStar,
83 MusehubViewEvent,
84 MusehubWatch,
85 MusehubWebhook,
86 MusehubWebhookDelivery,
87 )
88 from musehub.db.musehub_stash_models import MusehubStash, MusehubStashEntry
89
90
91 # ---------------------------------------------------------------------------
92 # Helpers
93 # ---------------------------------------------------------------------------
94
95 UTC = timezone.utc
96
97
98 def _now(days: int = 0, hours: int = 0) -> datetime:
99 return datetime.now(tz=UTC) - timedelta(days=days, hours=hours)
100
101
102 def _sha(seed: str) -> str:
103 return hashlib.sha256(seed.encode()).hexdigest()
104
105
106 def _uid(seed: str) -> str:
107 return str(uuid.UUID(bytes=hashlib.md5(seed.encode()).digest()))
108
109
110 # ---------------------------------------------------------------------------
111 # Constants — stable IDs so URLs never change between re-seeds
112 # ---------------------------------------------------------------------------
113
114 # Users — original community
115 GABRIEL = "user-gabriel-001"
116 SOFIA = "user-sofia-002"
117 MARCUS = "user-marcus-003"
118 YUKI = "user-yuki-004"
119 AALIYA = "user-aaliya-005"
120 CHEN = "user-chen-006"
121 FATOU = "user-fatou-007"
122 PIERRE = "user-pierre-008"
123
124 USERS = [
125 (GABRIEL, "gabriel", "Composer & producer. Neo-soul, modal jazz, ambient. All sounds generated by Muse."),
126 (SOFIA, "sofia", "Classical-meets-electronic. Algorithmic composition. Lover of Nils Frahm."),
127 (MARCUS, "marcus", "Session keys player turned digital composer. Jazz, funk, everything in between."),
128 (YUKI, "yuki", "Tokyo-based sound designer. Granular synthesis, algorithmic rhythms, and noise art."),
129 (AALIYA, "aaliya", "Afrobeat, highlife, and jazz fusion. Lagos → Berlin. Always in motion."),
130 (CHEN, "chen", "Microtonal explorer. Just intonation, spectral music, extended techniques."),
131 (FATOU, "fatou", "Griots meet Moog. West African rhythms with modular synthesis."),
132 (PIERRE, "pierre", "French chanson meets minimalism. Piano, cello, and long silences."),
133 ]
134
135 # Users — historical composers and licensed artists (batch-13 originals)
136 BACH = "user-bach-000000009"
137 CHOPIN = "user-chopin-00000010"
138 SCOTT_JOPLIN = "user-sjoplin-0000011"
139 KEVIN_MACLEOD = "user-kmacleod-000012"
140 KAI_ENGEL = "user-kaiengl-000013"
141
142 # Users — historical composers expansion (batch-14)
143 BEETHOVEN = "user-beethoven-000014"
144 MOZART = "user-mozart-0000015"
145 DEBUSSY = "user-debussy-0000016"
146 SATIE = "user-satie-00000017"
147 SCHUBERT = "user-schubert-000018"
148
149 COMPOSER_USERS = [
150 (BACH, "bach", "Johann Sebastian Bach (1685-1750). Baroque counterpoint, fugue, and harmony. Archive upload by Muse community."),
151 (CHOPIN, "chopin", "Frédéric Chopin (1810-1849). Romantic piano. Nocturnes, études, ballades. Archive upload by Muse community."),
152 (SCOTT_JOPLIN, "scott_joplin", "Scott Joplin (1868-1917). King of Ragtime. Maple Leaf Rag, The Entertainer. Archive upload by Muse community."),
153 (KEVIN_MACLEOD, "kevin_macleod", "Kevin MacLeod. Cinematic, orchestral, ambient. Thousands of royalty-free compositions. incompetech.com."),
154 (KAI_ENGEL, "kai_engel", "Kai Engel. Ambient, neoclassical, cinematic. Delicate textures and patient melodies. Free Music Archive."),
155 (BEETHOVEN, "beethoven", "Ludwig van Beethoven (1770-1827). Moonlight Sonata, Eroica, the 32 Piano Sonatas. Archive upload by Muse community."),
156 (MOZART, "mozart", "Wolfgang Amadeus Mozart (1756-1791). Piano Sonatas, String Quartets, Symphonies. Archive upload by Muse community."),
157 (DEBUSSY, "debussy", "Claude Debussy (1862-1918). Suite Bergamasque, Préludes, Images. Impressionism in sound. Archive upload by Muse community."),
158 (SATIE, "satie", "Erik Satie (1866-1925). Gymnopédies, Gnossiennes. Minimalism before minimalism. Archive upload by Muse community."),
159 (SCHUBERT, "schubert", "Franz Schubert (1797-1828). Impromptus, Wanderer Fantasy, Winterreise. Archive upload by Muse community."),
160 ]
161
162 # Rich profile metadata for all users — display names, locations, CC attribution, social links.
163 # Keyed by user_id (stable VARCHAR(36) identifier). Used when seeding musehub_profiles.
164 # CC attribution: "Public Domain" for composers expired >70 yrs; "CC BY 4.0" for explicitly licensed artists.
165 PROFILE_DATA: dict[str, dict[str, str | bool | None]] = {
166 GABRIEL: dict(
167 display_name="Gabriel",
168 bio="Building the infinite music machine. Neo-baroque meets modern production. Every session is a new fugue.",
169 location="San Francisco, CA",
170 website_url="https://muse.app",
171 twitter_handle="gabriel",
172 is_verified=False,
173 cc_license=None,
174 ),
175 SOFIA: dict(
176 display_name="Sofia",
177 bio="Counterpoint scholar and baroque revival composer. Polyphony enthusiast. Bach is the north star.",
178 location="Vienna, Austria",
179 website_url="https://muse.app/sofia",
180 twitter_handle="sofia_counterpoint",
181 is_verified=False,
182 cc_license=None,
183 ),
184 MARCUS: dict(
185 display_name="Marcus",
186 bio="EDM producer. Sampling the classics. 808s and Scarlatti. Ragtime breakbeats are a real genre now.",
187 location="Detroit, MI",
188 website_url="https://muse.app/marcus",
189 twitter_handle="marcus_808",
190 is_verified=False,
191 cc_license=None,
192 ),
193 YUKI: dict(
194 display_name="Yuki",
195 bio="Music theorist and Muse power user. Harmonic analysis obsessive. Every chord has a reason.",
196 location="Tokyo, Japan",
197 website_url="https://muse.app/yuki",
198 twitter_handle="yuki_harmony",
199 is_verified=False,
200 cc_license=None,
201 ),
202 AALIYA: dict(
203 display_name="Aaliya",
204 bio="Jazz fusion meets romantic piano. Coltrane changes on Chopin. Lagos-born, Berlin-based.",
205 location="Berlin, Germany",
206 website_url="https://muse.app/aaliya",
207 twitter_handle="aaliya_jazzpiano",
208 is_verified=False,
209 cc_license=None,
210 ),
211 CHEN: dict(
212 display_name="Chen",
213 bio="Film composer. Every emotion has a chord. Every scene has a theme. Microtonal when the script demands.",
214 location="Shanghai, China",
215 website_url="https://muse.app/chen",
216 twitter_handle="chen_filmscore",
217 is_verified=False,
218 cc_license=None,
219 ),
220 FATOU: dict(
221 display_name="Fatou",
222 bio="Afrobeats composer. Polyrhythm is natural. 7 over 4 makes sense to me. Griot traditions meet modular synthesis.",
223 location="Dakar, Senegal",
224 website_url="https://muse.app/fatou",
225 twitter_handle="fatou_polyrhythm",
226 is_verified=False,
227 cc_license=None,
228 ),
229 PIERRE: dict(
230 display_name="Pierre",
231 bio="French chanson meets minimalism. Piano, cello, and long silences. Satie would approve.",
232 location="Paris, France",
233 website_url="https://muse.app/pierre",
234 twitter_handle="pierre_chanson",
235 is_verified=False,
236 cc_license=None,
237 ),
238 BACH: dict(
239 display_name="Johann Sebastian Bach",
240 bio="Baroque composer. 48 preludes, 48 fugues. All 24 keys. Music is the arithmetic of the soul.",
241 location="Leipzig, Saxony (1723–1750)",
242 website_url="https://www.bach-digital.de",
243 twitter_handle=None,
244 is_verified=True,
245 cc_license="Public Domain",
246 ),
247 CHOPIN: dict(
248 display_name="Frédéric Chopin",
249 bio="Romantic pianist. Nocturnes, ballades, études. Expressive beyond measure. The piano speaks in my voice.",
250 location="Paris, France (1831–1849)",
251 website_url="https://chopin.nifc.pl",
252 twitter_handle=None,
253 is_verified=True,
254 cc_license="Public Domain",
255 ),
256 SCOTT_JOPLIN: dict(
257 display_name="Scott Joplin",
258 bio="King of Ragtime. Maple Leaf Rag. The Entertainer. Syncopation is poetry in motion.",
259 location="Sedalia, Missouri (1890s)",
260 website_url="https://www.scottjoplin.org",
261 twitter_handle=None,
262 is_verified=True,
263 cc_license="Public Domain",
264 ),
265 KEVIN_MACLEOD: dict(
266 display_name="Kevin MacLeod",
267 bio="Prolific composer. Every genre. Royalty-free forever. CC BY 4.0. If you use my music, just credit me.",
268 location="Sandpoint, Idaho",
269 website_url="https://incompetech.com",
270 twitter_handle="kmacleod",
271 is_verified=True,
272 cc_license="CC BY 4.0",
273 ),
274 KAI_ENGEL: dict(
275 display_name="Kai Engel",
276 bio="Ambient architect. Long-form textures. Silence is also music. Free Music Archive.",
277 location="Germany",
278 website_url="https://freemusicarchive.org/music/Kai_Engel",
279 twitter_handle=None,
280 is_verified=True,
281 cc_license="CC BY 4.0",
282 ),
283 BEETHOVEN: dict(
284 display_name="Ludwig van Beethoven",
285 bio="Transcending form and fate. 32 piano sonatas, 9 symphonies, 16 string quartets. The heroic style personified. Silence in the ears, thunder in the soul.",
286 location="Vienna, Austria (1792–1827)",
287 website_url="https://www.beethoven.de",
288 twitter_handle=None,
289 is_verified=True,
290 cc_license="Public Domain",
291 ),
292 MOZART: dict(
293 display_name="Wolfgang Amadeus Mozart",
294 bio="Clarity, wit, and perfection. 626 works catalogued by Köchel. K. 331 alone contains multitudes. Music is never too loud for me.",
295 location="Vienna, Austria (1781–1791)",
296 website_url="https://www.mozarteum.at",
297 twitter_handle=None,
298 is_verified=True,
299 cc_license="Public Domain",
300 ),
301 DEBUSSY: dict(
302 display_name="Claude Debussy",
303 bio="Music is the space between the notes. Impressionism without the label. Clair de Lune, Préludes, La Mer. The piano is a second sea.",
304 location="Paris, France (1884–1918)",
305 website_url="https://www.debussy.fr",
306 twitter_handle=None,
307 is_verified=True,
308 cc_license="Public Domain",
309 ),
310 SATIE: dict(
311 display_name="Erik Satie",
312 bio="Gymnopédies. Gnossiennes. Furniture music. I came into the world very young in a time that was very old. Simplicity is the highest art.",
313 location="Paris, France (Montmartre, 1887–1925)",
314 website_url="https://www.ericsatie.com",
315 twitter_handle=None,
316 is_verified=True,
317 cc_license="Public Domain",
318 ),
319 SCHUBERT: dict(
320 display_name="Franz Schubert",
321 bio="Impromptus, Wanderer Fantasy, Winterreise. Over 600 songs in 31 years of life. Music is the only door to the higher world of knowledge.",
322 location="Vienna, Austria (1797–1828)",
323 website_url="https://www.schubert-online.at",
324 twitter_handle=None,
325 is_verified=True,
326 cc_license="Public Domain",
327 ),
328 }
329
330 # All contributors for community-collab cycling (8 existing users)
331 ALL_CONTRIBUTORS = [
332 "gabriel", "sofia", "marcus", "yuki", "aaliya", "chen", "fatou", "pierre",
333 ]
334
335 # Repos — original community projects
336 REPO_NEO_SOUL = "repo-neo-soul-00000001"
337 REPO_MODAL_JAZZ = "repo-modal-jazz-000001"
338 REPO_AMBIENT = "repo-ambient-textures-1"
339 REPO_AFROBEAT = "repo-afrobeat-grooves-1"
340 REPO_MICROTONAL = "repo-microtonal-etudes1"
341 REPO_DRUM_MACHINE = "repo-drum-machine-00001"
342 REPO_CHANSON = "repo-chanson-minimale-1"
343 REPO_GRANULAR = "repo-granular-studies-1"
344 REPO_FUNK_SUITE = "repo-funk-suite-0000001"
345 REPO_JAZZ_TRIO = "repo-jazz-trio-0000001"
346 REPO_NEO_SOUL_FORK = "repo-neo-soul-fork-0001"
347 REPO_AMBIENT_FORK = "repo-ambient-fork-0001"
348
349 # Repos — 12 genre archive repos (batch-13)
350 REPO_WTC = "repo-well-tempered-cl01" # bach/well-tempered-clavier
351 REPO_GOLDBERG = "repo-goldberg-vars00001" # bach/goldberg-variations
352 REPO_NOCTURNES = "repo-chopin-nocturnes01" # chopin/nocturnes
353 REPO_MAPLE_LEAF = "repo-maple-leaf-rag0001" # scott_joplin/maple-leaf-rag
354 REPO_CIN_STRINGS = "repo-cinematic-strngs01" # kevin_macleod/cinematic-strings
355 REPO_KAI_AMBIENT = "repo-kai-ambient-txtr01" # kai_engel/ambient-textures
356 REPO_NEO_BAROQUE = "repo-neo-baroque-000001" # gabriel/neo-baroque
357 REPO_JAZZ_CHOPIN = "repo-jazz-chopin-000001" # aaliya/jazz-chopin
358 REPO_RAGTIME_EDM = "repo-ragtime-edm-000001" # marcus/ragtime-edm
359 REPO_FILM_SCORE = "repo-film-score-000001" # chen/film-score
360 REPO_POLYRHYTHM = "repo-polyrhythm-000001" # fatou/polyrhythm
361 REPO_COMMUNITY = "repo-community-collab01" # gabriel/community-collab
362
363 # Repos — archive expansion (batch-14: Beethoven, Mozart, Debussy, Satie, Schubert)
364 REPO_MOONLIGHT = "repo-moonlight-snt-001" # beethoven/moonlight-sonata
365 REPO_BEETHOVEN_SONATAS= "repo-beethvn-sonats01" # beethoven/piano-sonatas
366 REPO_MOZART_K331 = "repo-mozart-k331-0001" # mozart/piano-sonata-k331
367 REPO_SUITE_BERGAMASQUE= "repo-suite-bergam-001" # debussy/suite-bergamasque
368 REPO_GYMNOPEDIE = "repo-gymnopedie-0001" # satie/gymnopedie
369 REPO_IMPROMPTUS = "repo-impromptus-0001" # schubert/impromptus
370
371 # Repos — cross-composer remix projects (batch-15)
372 REPO_WELL_TEMPERED_RAG = "repo-well-tempd-rag01" # gabriel/well-tempered-rag
373 REPO_NOCTURNE_VARIATIONS= "repo-nocturne-vars001" # sofia/nocturne-variations
374 REPO_MOONLIGHT_JAZZ = "repo-moonlight-jazz01" # marcus/moonlight-jazz
375 REPO_CLAIR_DECONSTRUCTED= "repo-clair-decon-001" # yuki/clair-de-lune-deconstructed
376 REPO_RAGTIME_AFROBEAT = "repo-ragtime-afrobt01" # aaliya/ragtime-afrobeat
377
378 REPOS: list[dict[str, Any]] = [
379 dict(repo_id=REPO_NEO_SOUL, name="Neo-Soul Experiment", owner="gabriel", slug="neo-soul-experiment",
380 owner_user_id=GABRIEL, visibility="public",
381 description="A funk-influenced neo-soul project exploring polyrhythmic grooves in F# minor.",
382 tags=["neo-soul", "funk", "F# minor", "polyrhythm", "bass-heavy"],
383 key_signature="F# minor", tempo_bpm=92, days_ago=90, star_count=24, fork_count=3),
384 dict(repo_id=REPO_MODAL_JAZZ, name="Modal Jazz Sketches", owner="gabriel", slug="modal-jazz-sketches",
385 owner_user_id=GABRIEL, visibility="public",
386 description="Exploring Dorian and Phrygian modes. Miles Davis and Coltrane are the north stars.",
387 tags=["jazz", "modal", "Dorian", "Phrygian", "piano", "trumpet"],
388 key_signature="D Dorian", tempo_bpm=120, days_ago=60, star_count=18, fork_count=2),
389 dict(repo_id=REPO_AMBIENT, name="Ambient Textures Vol. 1", owner="sofia", slug="ambient-textures-vol-1",
390 owner_user_id=SOFIA, visibility="public",
391 description="Slow-evolving pads and generative arpeggios. Inspired by Eno and Nils Frahm.",
392 tags=["ambient", "generative", "pads", "Eb major", "slow"],
393 key_signature="Eb major", tempo_bpm=60, days_ago=45, star_count=31, fork_count=5),
394 dict(repo_id=REPO_AFROBEAT, name="Afrobeat Grooves", owner="aaliya", slug="afrobeat-grooves",
395 owner_user_id=AALIYA, visibility="public",
396 description="High-life meets contemporary production. Polyrhythmic percussion layers.",
397 tags=["afrobeat", "highlife", "polyrhythm", "Lagos", "percussion"],
398 key_signature="G major", tempo_bpm=128, days_ago=30, star_count=42, fork_count=6),
399 dict(repo_id=REPO_MICROTONAL, name="Microtonal Études", owner="chen", slug="microtonal-etudes",
400 owner_user_id=CHEN, visibility="public",
401 description="31-TET explorations. Spectral harmony and just intonation studies.",
402 tags=["microtonal", "spectral", "31-TET", "just intonation", "experimental"],
403 key_signature="C (31-TET)", tempo_bpm=76, days_ago=25, star_count=9, fork_count=1),
404 dict(repo_id=REPO_DRUM_MACHINE, name="808 Variations", owner="fatou", slug="808-variations",
405 owner_user_id=FATOU, visibility="public",
406 description="West African polyrhythm patterns through an 808 and modular synthesis.",
407 tags=["drums", "808", "polyrhythm", "West Africa", "modular"],
408 key_signature="A minor", tempo_bpm=100, days_ago=20, star_count=15, fork_count=2),
409 dict(repo_id=REPO_CHANSON, name="Chanson Minimale", owner="pierre", slug="chanson-minimale",
410 owner_user_id=PIERRE, visibility="public",
411 description="French chanson miniatures. Piano and cello. Silence as a compositional element.",
412 tags=["chanson", "minimalism", "piano", "cello", "French"],
413 key_signature="A major", tempo_bpm=52, days_ago=15, star_count=7, fork_count=0),
414 dict(repo_id=REPO_GRANULAR, name="Granular Studies", owner="yuki", slug="granular-studies",
415 owner_user_id=YUKI, visibility="public",
416 description="Granular synthesis research — texture, density, scatter. Source material: found sounds.",
417 tags=["granular", "synthesis", "experimental", "Tokyo", "texture"],
418 key_signature="E minor", tempo_bpm=70, days_ago=10, star_count=12, fork_count=1),
419 dict(repo_id=REPO_FUNK_SUITE, name="Funk Suite No. 1", owner="marcus", slug="funk-suite-no-1",
420 owner_user_id=MARCUS, visibility="public",
421 description="A four-movement funk suite. Electric piano, clavinet, wah bass, and pocket drums.",
422 tags=["funk", "electric piano", "clavinet", "groove", "suite"],
423 key_signature="E minor", tempo_bpm=108, days_ago=50, star_count=28, fork_count=4),
424 dict(repo_id=REPO_JAZZ_TRIO, name="Jazz Trio Sessions", owner="marcus", slug="jazz-trio-sessions",
425 owner_user_id=MARCUS, visibility="public",
426 description="Live-feel jazz trio recordings. Piano, double bass, brushed snare. Standards reimagined.",
427 tags=["jazz", "trio", "piano", "bass", "standards"],
428 key_signature="Bb major", tempo_bpm=138, days_ago=35, star_count=19, fork_count=2),
429 # Forked repos (private — for the fork sidebar section)
430 dict(repo_id=REPO_NEO_SOUL_FORK, name="Neo-Soul Experiment", owner="marcus", slug="neo-soul-experiment",
431 owner_user_id=MARCUS, visibility="private",
432 description="Fork of gabriel/neo-soul-experiment — Marcus's arrangement experiments.",
433 tags=["neo-soul", "funk", "F# minor", "fork"],
434 key_signature="F# minor", tempo_bpm=92, days_ago=10, star_count=0, fork_count=0),
435 dict(repo_id=REPO_AMBIENT_FORK, name="Ambient Textures Vol. 1", owner="yuki", slug="ambient-textures-vol-1",
436 owner_user_id=YUKI, visibility="private",
437 description="Fork of sofia/ambient-textures-vol-1 — Yuki's granular re-imagining.",
438 tags=["ambient", "granular", "fork"],
439 key_signature="Eb major", tempo_bpm=60, days_ago=5, star_count=0, fork_count=0),
440 ]
441
442 # Genre archive repos — batch-13 additions with structured muse_tags
443 GENRE_REPOS: list[dict[str, Any]] = [
444 dict(repo_id=REPO_WTC, name="The Well-Tempered Clavier", owner="bach", slug="well-tempered-clavier",
445 owner_user_id=BACH, visibility="public",
446 description="Bach's 48 preludes and fugues — one in each major and minor key. The definitive study in tonal harmony.",
447 tags=["genre:baroque", "key:C", "key:Am", "key:G", "key:F", "key:Bb", "stage:released", "emotion:serene", "emotion:complex"],
448 key_signature="C major (all 24 keys)", tempo_bpm=72, days_ago=365, star_count=88, fork_count=12),
449 dict(repo_id=REPO_GOLDBERG, name="Goldberg Variations", owner="bach", slug="goldberg-variations",
450 owner_user_id=BACH, visibility="public",
451 description="Aria with 30 variations. Bach's monumental keyboard work — from simple canon to ornate arabesque.",
452 tags=["genre:baroque", "stage:released", "emotion:joyful", "emotion:melancholic", "key:G"],
453 key_signature="G major", tempo_bpm=60, days_ago=350, star_count=74, fork_count=9),
454 dict(repo_id=REPO_NOCTURNES, name="Nocturnes", owner="chopin", slug="nocturnes",
455 owner_user_id=CHOPIN, visibility="public",
456 description="21 nocturnes for solo piano — poetry in sound. Lyrical melodies over arpeggiated left-hand accompaniment.",
457 tags=["genre:romantic", "emotion:melancholic", "emotion:tender", "stage:released", "key:Bb", "key:Eb"],
458 key_signature="Bb minor", tempo_bpm=58, days_ago=300, star_count=62, fork_count=8),
459 dict(repo_id=REPO_MAPLE_LEAF, name="Maple Leaf Rag", owner="scott_joplin", slug="maple-leaf-rag",
460 owner_user_id=SCOTT_JOPLIN, visibility="public",
461 description="The rag that launched a revolution. Syncopated right-hand melody over an oom-pah bass. The birth of ragtime.",
462 tags=["genre:ragtime", "emotion:playful", "stage:released", "key:Ab", "tempo:march"],
463 key_signature="Ab major", tempo_bpm=100, days_ago=280, star_count=51, fork_count=7),
464 dict(repo_id=REPO_CIN_STRINGS, name="Cinematic Strings", owner="kevin_macleod", slug="cinematic-strings",
465 owner_user_id=KEVIN_MACLEOD, visibility="public",
466 description="Orchestral string textures for cinematic use. Builds from delicate pianissimo to full tutti climax.",
467 tags=["genre:cinematic", "emotion:triumphant", "stage:released", "tempo:adagio", "key:D"],
468 key_signature="D minor", tempo_bpm=64, days_ago=180, star_count=43, fork_count=5),
469 dict(repo_id=REPO_KAI_AMBIENT, name="Ambient Textures", owner="kai_engel", slug="ambient-textures",
470 owner_user_id=KAI_ENGEL, visibility="public",
471 description="Patient, breathing soundscapes. Piano, strings, and silence woven into evolving ambient fields.",
472 tags=["genre:ambient", "emotion:serene", "stage:released", "tempo:largo", "key:C"],
473 key_signature="C major", tempo_bpm=50, days_ago=150, star_count=38, fork_count=4),
474 dict(repo_id=REPO_NEO_BAROQUE, name="Neo-Baroque Studies", owner="gabriel", slug="neo-baroque",
475 owner_user_id=GABRIEL, visibility="public",
476 description="What if Bach wrote jazz? Modal harmony and quartal voicings dressed in baroque counterpoint.",
477 tags=["genre:baroque", "genre:jazz", "stage:rough-mix", "emotion:complex", "ref:bach", "key:D"],
478 key_signature="D Dorian", tempo_bpm=84, days_ago=120, star_count=29, fork_count=3),
479 dict(repo_id=REPO_JAZZ_CHOPIN, name="Jazz Chopin", owner="aaliya", slug="jazz-chopin",
480 owner_user_id=AALIYA, visibility="public",
481 description="Chopin nocturnes reharmonized through a Coltrane lens. Rootless voicings, tritone substitutions, and more.",
482 tags=["genre:jazz", "genre:romantic", "emotion:tender", "ref:chopin", "ref:coltrane", "stage:mixing"],
483 key_signature="Bb minor", tempo_bpm=68, days_ago=90, star_count=34, fork_count=4),
484 dict(repo_id=REPO_RAGTIME_EDM, name="Ragtime EDM", owner="marcus", slug="ragtime-edm",
485 owner_user_id=MARCUS, visibility="public",
486 description="Scott Joplin meets the dancefloor. Syncopated MIDI melodies over trap hi-hats and house kick patterns.",
487 tags=["genre:edm", "genre:ragtime", "stage:production", "emotion:playful", "tempo:dance"],
488 key_signature="Ab major", tempo_bpm=128, days_ago=70, star_count=26, fork_count=3),
489 dict(repo_id=REPO_FILM_SCORE, name="Film Score — Untitled", owner="chen", slug="film-score",
490 owner_user_id=CHEN, visibility="public",
491 description="Three-act cinematic score. Microtonal tension in Act I, spectral climax in Act II, resolution in Act III.",
492 tags=["genre:cinematic", "emotion:tense", "emotion:triumphant", "stage:mixing", "key:C"],
493 key_signature="C (31-TET)", tempo_bpm=72, days_ago=55, star_count=18, fork_count=2),
494 dict(repo_id=REPO_POLYRHYTHM, name="Polyrhythm Studies", owner="fatou", slug="polyrhythm",
495 owner_user_id=FATOU, visibility="public",
496 description="West African rhythmic philosophy through a modular lens. 7-over-4, 5-over-3, and beyond.",
497 tags=["genre:afrobeats", "emotion:playful", "emotion:energetic", "stage:rough-mix", "tempo:polyrhythm"],
498 key_signature="A minor", tempo_bpm=92, days_ago=35, star_count=21, fork_count=2),
499 dict(repo_id=REPO_COMMUNITY, name="Community Collab", owner="gabriel", slug="community-collab",
500 owner_user_id=GABRIEL, visibility="public",
501 description="An open canvas for all eight contributors. Every genre, every voice, one evolving composition.",
502 tags=["genre:baroque", "genre:jazz", "genre:romantic", "genre:ragtime", "genre:cinematic",
503 "genre:ambient", "genre:edm", "genre:afrobeats", "emotion:serene", "emotion:complex",
504 "emotion:joyful", "emotion:melancholic", "emotion:tender", "emotion:playful",
505 "emotion:energetic", "emotion:triumphant", "emotion:tense", "stage:rough-mix"],
506 key_signature="C major", tempo_bpm=90, days_ago=200, star_count=95, fork_count=15),
507 # Archive expansion — batch-14 (Beethoven, Mozart, Debussy, Satie, Schubert)
508 dict(repo_id=REPO_MOONLIGHT, name="Moonlight Sonata", owner="beethoven", slug="moonlight-sonata",
509 owner_user_id=BEETHOVEN, visibility="public",
510 description="Piano Sonata No. 14 in C# minor, Op. 27 No. 2. Three movements: Adagio sostenuto, Allegretto, Presto agitato. MIDI source: piano-midi.de (Bernd Krueger, CC).",
511 tags=["genre:classical", "emotion:melancholic", "emotion:tense", "stage:released",
512 "key:C#m", "tempo:adagio", "ref:beethoven"],
513 key_signature="C# minor", tempo_bpm=54, days_ago=400, star_count=112, fork_count=18),
514 dict(repo_id=REPO_BEETHOVEN_SONATAS, name="Piano Sonatas", owner="beethoven", slug="piano-sonatas",
515 owner_user_id=BEETHOVEN, visibility="public",
516 description="The complete 32 Piano Sonatas — from Op. 2 No. 1 in F minor to Op. 111 in C minor. Each sonata as a branch. The definitive keyboard cycle of the Classical era. MIDI source: Mutopia Project (CC).",
517 tags=["genre:classical", "emotion:complex", "emotion:triumphant", "stage:released",
518 "key:F", "key:C", "key:Eb", "ref:beethoven"],
519 key_signature="Various", tempo_bpm=108, days_ago=380, star_count=87, fork_count=11),
520 dict(repo_id=REPO_MOZART_K331, name="Piano Sonata K. 331", owner="mozart", slug="piano-sonata-k331",
521 owner_user_id=MOZART, visibility="public",
522 description="Sonata in A major, K. 331. Theme with 6 variations, Menuetto, and the famous Rondo alla Turca (Turkish March). Variations as commits is the ideal Muse demo. MIDI source: Mutopia Project (CC).",
523 tags=["genre:classical", "emotion:playful", "emotion:joyful", "stage:released",
524 "key:A", "tempo:andante", "ref:mozart"],
525 key_signature="A major", tempo_bpm=104, days_ago=390, star_count=73, fork_count=10),
526 dict(repo_id=REPO_SUITE_BERGAMASQUE, name="Suite Bergamasque", owner="debussy", slug="suite-bergamasque",
527 owner_user_id=DEBUSSY, visibility="public",
528 description="Four piano pieces: Prélude, Menuet, Clair de Lune, Passepied. Clair de Lune in Db major is among the most recognised piano pieces ever written. MIDI source: piano-midi.de (CC).",
529 tags=["genre:classical", "emotion:serene", "emotion:tender", "stage:released",
530 "key:Db", "key:F", "tempo:andante", "ref:debussy"],
531 key_signature="Db major", tempo_bpm=54, days_ago=340, star_count=99, fork_count=14),
532 dict(repo_id=REPO_GYMNOPEDIE, name="Gymnopédies", owner="satie", slug="gymnopedie",
533 owner_user_id=SATIE, visibility="public",
534 description="Three slow, melancholic piano pieces in 3/4. Gymnopédie No. 1 in D major, No. 2 in C major, No. 3 in G major. Minimalism before the word existed. MIDI source: Mutopia Project (CC).",
535 tags=["genre:classical", "emotion:serene", "emotion:melancholic", "stage:released",
536 "key:D", "key:C", "key:G", "tempo:lent", "ref:satie"],
537 key_signature="D major", tempo_bpm=52, days_ago=310, star_count=81, fork_count=12),
538 dict(repo_id=REPO_IMPROMPTUS, name="Impromptus Op. 90", owner="schubert", slug="impromptus",
539 owner_user_id=SCHUBERT, visibility="public",
540 description="Four impromptus for solo piano, D. 899 (Op. 90). C minor, Eb major, Gb major, Ab major — a full emotional arc. The Romantic piano at its most intimate. MIDI source: Mutopia Project (CC).",
541 tags=["genre:romantic", "emotion:tender", "emotion:melancholic", "stage:released",
542 "key:Cm", "key:Eb", "key:Ab", "ref:schubert"],
543 key_signature="C minor", tempo_bpm=62, days_ago=330, star_count=58, fork_count=7),
544 # Cross-composer remix repos — batch-15 (showing Muse VCS power across eras)
545 dict(repo_id=REPO_WELL_TEMPERED_RAG, name="Well-Tempered Rag", owner="gabriel", slug="well-tempered-rag",
546 owner_user_id=GABRIEL, visibility="public",
547 description="Bach's counterpoint bass lines meet Joplin's syncopated right hand. Forked from bach/goldberg-variations. The commit graph tells the story of Baroque meeting Ragtime.",
548 tags=["genre:baroque", "genre:ragtime", "emotion:playful", "emotion:complex",
549 "stage:production", "ref:bach", "ref:joplin"],
550 key_signature="G major", tempo_bpm=96, days_ago=85, star_count=47, fork_count=6),
551 dict(repo_id=REPO_NOCTURNE_VARIATIONS, name="Nocturne Variations", owner="sofia", slug="nocturne-variations",
552 owner_user_id=SOFIA, visibility="public",
553 description="Chopin's Nocturne No. 20 in C# minor (Posthumous) merged with Satie's Gymnopédie No. 1. Both in their respective minors, both patient and interior. The merge commit is the heart of this repo.",
554 tags=["genre:romantic", "genre:classical", "emotion:melancholic", "emotion:serene",
555 "stage:mixing", "ref:chopin", "ref:satie"],
556 key_signature="C# minor", tempo_bpm=54, days_ago=72, star_count=39, fork_count=5),
557 dict(repo_id=REPO_MOONLIGHT_JAZZ, name="Moonlight Jazz", owner="marcus", slug="moonlight-jazz",
558 owner_user_id=MARCUS, visibility="public",
559 description="Beethoven's Moonlight Sonata, Mvt. 1 — arpeggios become jazz comping. Tritone substitutions on the harmony, rootless voicings, and a walking bass. Forked from beethoven/moonlight-sonata.",
560 tags=["genre:classical", "genre:jazz", "emotion:melancholic", "emotion:tender",
561 "stage:production", "ref:beethoven", "ref:coltrane"],
562 key_signature="C# minor", tempo_bpm=60, days_ago=58, star_count=52, fork_count=7),
563 dict(repo_id=REPO_CLAIR_DECONSTRUCTED, name="Clair de Lune Deconstructed", owner="yuki", slug="clair-de-lune-deconstructed",
564 owner_user_id=YUKI, visibility="public",
565 description="Debussy's Clair de Lune atomized into granular ambient textures. Each phrase becomes grain scatter; each harmonic color becomes a pad layer. Forked from debussy/suite-bergamasque.",
566 tags=["genre:ambient", "genre:classical", "emotion:serene", "emotion:mysterious",
567 "stage:rough-mix", "ref:debussy"],
568 key_signature="Db major", tempo_bpm=50, days_ago=44, star_count=33, fork_count=4),
569 dict(repo_id=REPO_RAGTIME_AFROBEAT, name="Ragtime Afrobeat", owner="aaliya", slug="ragtime-afrobeat",
570 owner_user_id=AALIYA, visibility="public",
571 description="Joplin's Maple Leaf Rag polyrhythm layered with West African 12/8. The groove-check panel shows 2-against-3 cross-rhythm. Forked from scott_joplin/maple-leaf-rag.",
572 tags=["genre:ragtime", "genre:afrobeats", "emotion:playful", "emotion:energetic",
573 "stage:production", "ref:joplin"],
574 key_signature="Ab major", tempo_bpm=112, days_ago=37, star_count=61, fork_count=8),
575 ]
576
577
578 # ---------------------------------------------------------------------------
579 # Commit templates per repo
580 # ---------------------------------------------------------------------------
581
582 def _make_commits(repo_id: str, repo_key: str, n: int) -> list[dict[str, Any]]:
583 """Generate n realistic commits for repo_key with a branching history."""
584 TEMPLATES = {
585 "neo-soul": [
586 ("init: establish F# minor groove template at 92 BPM", "gabriel"),
587 ("feat(bass): add polyrhythmic bass line — 3-against-4 pulse", "gabriel"),
588 ("feat(keys): Rhodes chord voicings with upper-structure triads", "gabriel"),
589 ("refactor(drums): humanize ghost notes, tighten hi-hat velocity", "gabriel"),
590 ("feat(strings): bridge string section — section:bridge track:strings", "gabriel"),
591 ("feat(horns): sketch trumpet + alto sax counter-melody", "gabriel"),
592 ("fix(keys): resolve voice-leading parallel fifths in bar 7", "gabriel"),
593 ("feat(guitar): add scratch guitar rhythm in chorus — track:guitar", "gabriel"),
594 ("refactor(bass): tighten sub-bass at bar 13 to avoid muddiness", "marcus"),
595 ("feat(perc): add shaker and tambourine for groove density", "gabriel"),
596 ("fix(timing): realign hi-hat to quantize grid after humanize", "gabriel"),
597 ("feat(choir): add background vocal pad — ooh/aah — section:chorus", "gabriel"),
598 ("refactor(mix): reduce Rhodes level -3dB, open hi-hat +2dB", "marcus"),
599 ("feat(bridge): call-and-response horn arrangement — bars 25-32", "gabriel"),
600 ("fix(harmony): correct augmented chord spelling in turnaround", "gabriel"),
601 ("feat(strings): counterpoint violin line against bass in verse", "gabriel"),
602 ("refactor(drums): add kick variation in bar 4 of each 8-bar phrase", "marcus"),
603 ("feat(organ): add organ swell in pre-chorus — track:organ", "gabriel"),
604 ("fix(keys): remove accidental octave doubling in Rhodes voicing", "gabriel"),
605 ("feat(bass): slap variation for funk breakdown — section:breakdown", "marcus"),
606 ("refactor(horns): rewrite alto sax response phrase — cleaner contour", "gabriel"),
607 ("feat(perc): cowbell accent on beat 3 of bar 2 — groove:funk", "gabriel"),
608 ("feat(strings): pizzicato countermelody — bars 17-24", "gabriel"),
609 ("fix(guitar): tighten wah envelope attack — reduce pre-delay", "gabriel"),
610 ("feat(voice): lead vocal melody sketch — section:verse track:vocals", "gabriel"),
611 ],
612 "modal-jazz": [
613 ("init: D Dorian vamp at 120 BPM — piano + bass", "gabriel"),
614 ("feat(melody): Coltrane-inspired pentatonic runs over IV chord", "gabriel"),
615 ("feat(drums): brush kit — swing factor 0.65", "gabriel"),
616 ("experiment: Phrygian dominant bridge — E Phrygian Dominant", "gabriel"),
617 ("feat(piano): add McCoy Tyner quartal voicings in A section", "gabriel"),
618 ("fix(bass): correct walking bass note on bar 9 beat 3", "gabriel"),
619 ("feat(trumpet): head melody sketch — 12-bar AABA form", "gabriel"),
620 ("refactor(drums): increase ride cymbal bell accent frequency", "gabriel"),
621 ("feat(piano): bebop left-hand comp pattern — bars 1-8", "marcus"),
622 ("fix(melody): resolve blue note to major 3rd at phrase end", "gabriel"),
623 ("feat(bass): pedal point through Phrygian section — E pedal", "gabriel"),
624 ("feat(drums): hi-hat splash on beat 4 of turnaround", "gabriel"),
625 ("refactor(piano): revoice III chord as tritone substitution", "gabriel"),
626 ("feat(guitar): Freddie Green-style chord stabs — 4-to-the-bar", "marcus"),
627 ("fix(trumpet): fix pitch of low C# — use Eb enharmonic", "gabriel"),
628 ("feat(piano): out-chorus with McCoy quartal clusters", "gabriel"),
629 ("feat(bass): counter-rhythm 2-bar fill after trumpet solo", "gabriel"),
630 ],
631 "ambient": [
632 ("init: Eb major pad foundation — slow attack 4s release 8s", "sofia"),
633 ("feat(arp): generative 16th-note arpeggiator — random seed 42", "sofia"),
634 ("feat(texture): granular string texture layer — section:intro", "yuki"),
635 ("fix(arp): reduce velocity variance — sounds too mechanical", "sofia"),
636 ("feat(pad): add sub-octave layer for warmth — section:middle", "sofia"),
637 ("feat(bells): wind chime texture — 7th partial harmonic series", "yuki"),
638 ("refactor(arp): increase note-length randomization range", "sofia"),
639 ("feat(drone): Eb pedal drone — bowed brass harmonic", "pierre"),
640 ("fix(texture): reduce granular density in intro — too busy", "yuki"),
641 ("feat(reverb): add convolution reverb impulse — Norwegian church", "sofia"),
642 ("feat(pad): second pad layer — inversion of root chord", "sofia"),
643 ("refactor(arp): change arpeggio direction — ascending + descending", "sofia"),
644 ("feat(texture): filtered noise texture — high shelf +6dB", "yuki"),
645 ("fix(drone): tune drone to equal temperament Eb", "sofia"),
646 ("feat(melody): sparse piano melody — whole notes — section:climax", "pierre"),
647 ("feat(fade): 3-minute fade out — linear to -80dB", "sofia"),
648 ("refactor(mix): reduce string texture -2dB to sit behind pad", "yuki"),
649 ("fix(arp): fix stuck note at bar 64 — midi note-off missing", "sofia"),
650 ],
651 "afrobeat": [
652 ("init: G major groove at 128 BPM — 12/8 polyrhythm", "aaliya"),
653 ("feat(perc): traditional talking drum pattern — track:tama", "aaliya"),
654 ("feat(guitar): highlife guitar pattern — interlocking rhythm", "aaliya"),
655 ("feat(bass): electric bass groove — root-fifth walking pattern", "aaliya"),
656 ("feat(horns): brass unison figure — bars 1-4", "aaliya"),
657 ("refactor(perc): tighten conga timing — reduce humanize variance", "fatou"),
658 ("feat(keys): Fender Rhodes stabs — track:keys", "aaliya"),
659 ("fix(guitar): fix choke on open string — add palm mute", "aaliya"),
660 ("feat(choir): call-and-response vocal arrangement", "aaliya"),
661 ("feat(bass): syncopated fills at section transitions", "aaliya"),
662 ("refactor(horns): split alto and tenor lines — 3rd apart", "aaliya"),
663 ("feat(perc): shekere layer — steady eighth-note pulse", "fatou"),
664 ("fix(mix): reduce vocal level in verse — instrumental focus", "aaliya"),
665 ("feat(guitar): second guitar — rhythmic scratches on offbeat", "aaliya"),
666 ("feat(bass): slap bass hook for chorus energy boost", "aaliya"),
667 ("refactor(drums): add more snare ghost notes — Questlove style", "fatou"),
668 ("feat(keys): organ swell into chorus — track:organ", "aaliya"),
669 ("fix(perc): fix timing drift on conga in bar 32", "fatou"),
670 ("feat(strings): string overdub — Fela-inspired octave line", "aaliya"),
671 ("feat(voice): lead vocal melody — Yoruba lyric sketch", "aaliya"),
672 ],
673 "microtonal": [
674 ("init: C (31-TET) drone exploration at 76 BPM", "chen"),
675 ("feat(harmony): otonal hexad — 4:5:6:7:9:11", "chen"),
676 ("feat(melody): quarter-tone scale ascending line", "chen"),
677 ("fix(tuning): correct 7th partial — was off by 3 cents", "chen"),
678 ("feat(rhythm): Messiaen mode 3 rhythm grid", "chen"),
679 ("feat(texture): spectral filtered noise — harmonic series", "chen"),
680 ("refactor(melody): retrograde inversion of opening motif", "chen"),
681 ("feat(bass): undertone series pedal — utonal foundation", "chen"),
682 ("fix(harmony): resolve voice-leading microtonal step error", "chen"),
683 ("feat(perc): non-retrogradable rhythm in timpani", "chen"),
684 ("feat(strings): col legno battuto technique — quarter-tone gliss", "chen"),
685 ("refactor(harmony): substitute Ptolemy's intense chromatic", "chen"),
686 ("feat(woodwinds): multiphonics — clarinet + flute", "chen"),
687 ("fix(tuning): recalibrate piano to equal 31-TET temperament", "chen"),
688 ],
689 "drums": [
690 ("init: A minor 808 foundation at 100 BPM", "fatou"),
691 ("feat(kick): four-on-the-floor with sub-frequency duck", "fatou"),
692 ("feat(snare): syncopated snare with flam accent", "fatou"),
693 ("feat(hihat): 16th-note hi-hat with velocity curve", "fatou"),
694 ("feat(perc): djembe pattern — traditional Mandinka rhythm", "fatou"),
695 ("feat(808): 808 bass note on root — 100ms decay", "fatou"),
696 ("refactor(kick): tune 808 kick to key center A — 110Hz", "fatou"),
697 ("fix(hihat): remove double-triggered hi-hat on beat 3", "fatou"),
698 ("feat(perc): shaker accent pattern — offbeat sixteenths", "fatou"),
699 ("feat(snare): ghost note velocity humanize — ±12 velocity", "fatou"),
700 ("feat(808): sub-bass movement — root to 5th fills", "fatou"),
701 ("refactor(perc): layer djembe with finger drum machine", "fatou"),
702 ("feat(crash): crash on bar 9 downbeat — section transition", "fatou"),
703 ],
704 "chanson": [
705 ("init: A major sketch — piano solo motif at 52 BPM", "pierre"),
706 ("feat(piano): left-hand ostinato — arpeggiated 9th chord", "pierre"),
707 ("feat(cello): pizzicato bass line — bars 1-8", "pierre"),
708 ("feat(piano): theme A — 8-bar melody in upper voice", "pierre"),
709 ("feat(cello): sustained cello counterpoint — bars 9-16", "pierre"),
710 ("refactor(piano): reduce left-hand density — let melody breathe", "pierre"),
711 ("feat(piano): theme B in relative minor — F# minor", "pierre"),
712 ("fix(cello): bowings — ensure smooth legato at bar 12", "pierre"),
713 ("feat(piano): coda — augmented theme A in parallel 10ths", "pierre"),
714 ("feat(silence): 4-bar rest before final chord — dynamic contrast", "pierre"),
715 ("feat(cello): col legno tremolo — extended technique", "pierre"),
716 ("refactor(harmony): substitute V7 with bVII for chanson flavour", "pierre"),
717 ],
718 "granular": [
719 ("init: E minor granular pad — source: rain recording", "yuki"),
720 ("feat(scatter): random scatter algorithm — grain size 20-80ms", "yuki"),
721 ("feat(density): grain density envelope — sparse to dense", "yuki"),
722 ("feat(pitch): pitch randomization ±0.3 semitones", "yuki"),
723 ("feat(texture): city ambience layer — Tokyo train station", "yuki"),
724 ("fix(phase): fix grain phase correlation — reduce flamming", "yuki"),
725 ("feat(filter): formant filter on granular output — vowel morph", "yuki"),
726 ("refactor(scatter): increase random seed variation per bar", "yuki"),
727 ("feat(rhythm): rhythmic granular — sync to 70 BPM sixteenths", "yuki"),
728 ("fix(tuning): retune pitch center to E — was detuned +0.5st", "yuki"),
729 ("feat(reverb): 8-second hall reverb tail — late reflections only", "yuki"),
730 ("feat(mod): LFO modulation on grain position — 0.3Hz triangle", "yuki"),
731 ],
732 "funk-suite": [
733 ("init: E minor funk groove at 108 BPM — Mvt. I", "marcus"),
734 ("feat(bass): wah-wah bass hook — bars 1-4 — track:bass", "marcus"),
735 ("feat(keys): electric piano chord voicings — tight stabs", "marcus"),
736 ("feat(clavinet): clavinet riff — bars 5-8", "marcus"),
737 ("feat(drums): pocket drum groove — ghost notes on snare", "marcus"),
738 ("feat(guitar): rhythm guitar — interlocking with clavinet", "marcus"),
739 ("feat(horns): brass hits on the upbeat — track:horns", "marcus"),
740 ("refactor(bass): tighten wah envelope attack for more snap", "marcus"),
741 ("feat(keys): Rhodes solo in Mvt. II — Dorian mode", "marcus"),
742 ("fix(guitar): remove string buzz on open D", "marcus"),
743 ("feat(bass): slap funk breakdown — Mvt. II outro", "marcus"),
744 ("feat(perc): add congas — Afro-Cuban polyrhythm layer", "marcus"),
745 ("feat(keys): B3 organ swell — Mvt. III transition", "marcus"),
746 ("refactor(drums): accent hi-hat on the e's — open 16th feel", "marcus"),
747 ("fix(horns): retune brass — flat by 8 cents on high notes", "marcus"),
748 ("feat(bass): octave bass walk into chorus — track:bass", "marcus"),
749 ("feat(clavinet): filtered clavinet — muted pickstyle", "marcus"),
750 ("fix(keys): fix missed chord on beat 4 bar 22", "marcus"),
751 ("feat(drums): Mvt. IV — double-time feel — hi-hat 16th groove", "marcus"),
752 ("feat(bass): fretless bass for Mvt. IV — floating groove", "marcus"),
753 ],
754 "jazz-trio": [
755 ("init: Bb major vamp — piano trio at 138 BPM", "marcus"),
756 ("feat(piano): comping pattern — shell voicings 3-7", "marcus"),
757 ("feat(bass): walking bass — Bb major standard changes", "marcus"),
758 ("feat(drums): brushed snare pattern — triplet feel", "marcus"),
759 ("feat(piano): solo chorus 1 — pentatonic approach", "marcus"),
760 ("feat(bass): bass solo feature — rubato", "marcus"),
761 ("feat(drums): trading 4s — kit break response", "marcus"),
762 ("refactor(piano): reharmonize bridge — tritone subs", "marcus"),
763 ("feat(piano): stride left hand in final chorus", "marcus"),
764 ("fix(bass): fix intonation on F# — adjust finger placement", "marcus"),
765 ("feat(drums): add brushed cymbal roll into solo sections", "marcus"),
766 ("feat(piano): ballad tempo reduction for outro — ♩=72", "marcus"),
767 ("refactor(bass): add counterpoint line during piano comping", "marcus"),
768 ("fix(drums): remove extraneous kick note on bar 9", "marcus"),
769 ("feat(piano): final cadenza — rubato", "marcus"),
770 ],
771 # Genre archive repos — batch-13
772 "wtc": [
773 ("init: Book I — Prelude No.1 in C major — arpeggiated harmony", "gabriel"),
774 ("feat(fugue): Fugue No.1 in C major — 4-voice exposition", "gabriel"),
775 ("feat(prelude): Prelude No.2 in C minor — perpetual motion 16ths", "sofia"),
776 ("feat(fugue): Fugue No.2 in C minor — chromatic subject", "gabriel"),
777 ("feat(prelude): Prelude No.3 in C# major — arpeggiated texture", "chen"),
778 ("feat(fugue): Fugue No.3 in C# major — 3-voice stretto", "gabriel"),
779 ("refactor(harmony): correct spelling of diminished 7th in bar 8", "pierre"),
780 ("feat(prelude): Prelude No.4 in C# minor — lyrical melody", "gabriel"),
781 ("feat(fugue): Fugue No.4 in C# minor — 5-voice exposition", "sofia"),
782 ("feat(prelude): Prelude No.5 in D major — driving 16th notes", "gabriel"),
783 ("feat(fugue): Fugue No.5 in D major — invertible counterpoint", "chen"),
784 ("fix(voice-leading): resolve parallel 5ths in C major fugue bar 14", "gabriel"),
785 ("feat(prelude): Prelude No.6 in D minor — expressive chromatics", "pierre"),
786 ("feat(fugue): Fugue No.6 in D minor — augmentation in bass", "gabriel"),
787 ("feat(prelude): Prelude No.7 in Eb major — ornate passagework", "sofia"),
788 ("feat(fugue): Fugue No.7 in Eb major — inversion of subject", "gabriel"),
789 ("refactor(ornamentation): add trills per Baroque convention — bars 1-4", "chen"),
790 ("feat(prelude): Prelude No.8 in Eb minor — chromatic descent", "gabriel"),
791 ("feat(fugue): Fugue No.8 in Eb minor — 3-voice with episode", "pierre"),
792 ("feat(prelude): Prelude No.9 in E major — binary form", "gabriel"),
793 ("feat(fugue): Fugue No.9 in E major — motivic development", "sofia"),
794 ("fix(tuning): retune to equal temperament from well-temperament", "chen"),
795 ("feat(prelude): Prelude No.10 in E minor — two-part invention style", "gabriel"),
796 ("feat(fugue): Fugue No.10 in E minor — rhythmic diminution", "gabriel"),
797 ("feat(book2): Book II — Prelude No.1 in C major — extended version", "sofia"),
798 ("feat(book2): Fugue No.1 BK2 in C major — 4-voice with tonal answer", "gabriel"),
799 ("feat(book2): Prelude No.2 BK2 in C minor — turbulent arpeggios", "pierre"),
800 ("feat(book2): Fugue No.2 BK2 — chromatic subject, 4 voices", "gabriel"),
801 ("refactor(dynamics): add hairpin dynamics per Urtext edition", "sofia"),
802 ("feat(book2): Prelude No.3 BK2 in C# major — serene cantabile", "gabriel"),
803 ],
804 "goldberg": [
805 ("init: Goldberg Aria — sarabande in G major, ornate upper voice", "gabriel"),
806 ("feat(var1): Variation 1 — two-voice in parallel 3rds", "gabriel"),
807 ("feat(var2): Variation 2 — one voice per hand, canonic imitation", "sofia"),
808 ("feat(var3): Variation 3 — canon at the unison", "gabriel"),
809 ("feat(var4): Variation 4 — robust 4-voice passepied", "pierre"),
810 ("feat(var5): Variation 5 — hand-crossing, one voice each hand", "gabriel"),
811 ("feat(var6): Variation 6 — canon at the second", "sofia"),
812 ("feat(var7): Variation 7 — gigue in 6/8, dance character", "gabriel"),
813 ("feat(var8): Variation 8 — two-voice inversion in 3rds and 6ths", "chen"),
814 ("feat(var13): Variation 13 — lyrical aria-like melody, experimental rubato", "gabriel"),
815 ("fix(ornaments): correct trill resolution in Variation 13 bar 9", "sofia"),
816 ("feat(var25): Variation 25 — chromatic aria, the emotional heart", "gabriel"),
817 ("feat(var30): Variation 30 — Quodlibet, quotes folk songs", "gabriel"),
818 ("feat(aria-reprise): Aria da capo — return of opening theme", "pierre"),
819 ("refactor(voicing): ensure aria melody sits above all inner voices", "gabriel"),
820 ("fix(voice-leading): remove parallel octaves in Variation 4 bar 12", "sofia"),
821 ("refactor(tempo): apply consistent note values in 3/4 Variations", "gabriel"),
822 ("feat(var21): Variation 21 — chromatic canon at the 7th", "chen"),
823 ],
824 "nocturnes": [
825 ("init: Op.9 No.1 in Bb minor — gentle arpeggiated bass, yearning melody", "aaliya"),
826 ("feat(op9-2): Op.9 No.2 in Eb major — the iconic theme, ornate reprise", "aaliya"),
827 ("feat(op9-3): Op.9 No.3 in B major — agitated middle section", "gabriel"),
828 ("feat(op15-1): Op.15 No.1 in F major — pastoral melody, stormy development", "aaliya"),
829 ("feat(op15-2): Op.15 No.2 in F# major — murmuring bass, cantabile melody", "sofia"),
830 ("feat(op15-3): Op.15 No.3 in G minor — solemn choral opening", "aaliya"),
831 ("feat(op27-1): Op.27 No.1 in C# minor — tragic opening, ecstatic climax", "gabriel"),
832 ("feat(op27-2): Op.27 No.2 in Db major — sustained melody, ornate inner voice", "aaliya"),
833 ("refactor(rubato): add tempo fluctuation markings per Chopin's own notation", "pierre"),
834 ("fix(pedaling): correct sustain pedal placement in Op.9 No.2 bar 5", "aaliya"),
835 ("feat(op32-1): Op.32 No.1 in B major — introspective, questioning end", "sofia"),
836 ("feat(op32-2): Op.32 No.2 in Ab major — gentle but harmonically complex", "aaliya"),
837 ("feat(op37-1): Op.37 No.1 in G minor — chorale-like, organistic", "gabriel"),
838 ("feat(op37-2): Op.37 No.2 in G major — barcarolle-style 6/8", "aaliya"),
839 ("refactor(ornamentation): add mordents and grace notes per autograph", "sofia"),
840 ("fix(voice-leading): eliminate voice crossing in Op.15 No.3 bar 7", "aaliya"),
841 ("feat(op48-1): Op.48 No.1 in C minor — grand and tragic", "gabriel"),
842 ("feat(op48-2): Op.48 No.2 in F# minor — agitated and restless", "aaliya"),
843 ("feat(op55-1): Op.55 No.1 in F minor — melancholic cantabile", "sofia"),
844 ("feat(op55-2): Op.55 No.2 in Eb major — flowing, conversational", "aaliya"),
845 ("feat(op62-1): Op.62 No.1 in B major — late style, fragmented ornament", "gabriel"),
846 ("feat(op62-2): Op.62 No.2 in E major — tender farewell, inner voices", "aaliya"),
847 ],
848 "maple-leaf": [
849 ("init: Maple Leaf Rag in Ab major — 4/4 at 100 BPM", "marcus"),
850 ("feat(A): Section A — syncopated melody over oom-pah bass, bars 1-16", "marcus"),
851 ("feat(A-repeat): Section A repeat with octave doubling in melody", "gabriel"),
852 ("feat(B): Section B — contrast, moves to Eb major", "marcus"),
853 ("feat(B-repeat): Section B repeat — velocity humanized", "marcus"),
854 ("feat(C): Section C (trio) — moves to Db major, more lyrical", "marcus"),
855 ("feat(C-repeat): Section C repeat with improvised embellishment", "gabriel"),
856 ("feat(D): Section D — returns to Ab, triumphant restatement", "marcus"),
857 ("refactor(bass): tighten oom-pah bass timing — was 8ms ahead", "marcus"),
858 ("fix(melody): correct grace note in bar 9 — was wrong pitch Eb not D", "gabriel"),
859 ("feat(slow): slow-version — halftime feel, rubato allowed", "marcus"),
860 ("feat(slow): slow-version extended ornaments in melody", "gabriel"),
861 ("feat(edm): marcus-edm-remix — trap hi-hats under ragtime melody", "marcus"),
862 ("feat(edm): marcus-edm-remix — 808 bass replacing oom-pah pattern", "marcus"),
863 ],
864 "cinematic-strings": [
865 ("init: Cinematic Strings in D minor — string orchestra at 64 BPM", "gabriel"),
866 ("feat(intro): solo cello theme — bars 1-8 — pp, col arco", "chen"),
867 ("feat(build): violas enter — pizzicato counter-rhythm, bars 9-16", "gabriel"),
868 ("feat(build): second violins add sustained harmonic pad", "sofia"),
869 ("feat(climax): full orchestra tutti — bars 33-40 — ff", "gabriel"),
870 ("feat(climax): timpani and brass reinforcement at climax peak", "chen"),
871 ("feat(resolution): strings return to solo cello — reprise of theme", "gabriel"),
872 ("refactor(dynamics): smooth crescendo from pp to ff over 32 bars", "sofia"),
873 ("fix(intonation): retune violin II section — was sharp by 5 cents", "gabriel"),
874 ("feat(orchestral): orchestral branch — add oboe and clarinet doubling", "chen"),
875 ("feat(orchestral): French horn countermelody in orchestral version", "gabriel"),
876 ("feat(piano): stripped-piano branch — piano reduction of string score", "pierre"),
877 ("feat(piano): add pedal markings to piano reduction", "sofia"),
878 ("refactor(tempo): add ritardando at bar 38 for dramatic pause", "gabriel"),
879 ("fix(articulation): add sul ponticello marking to Variation 2 strings", "chen"),
880 ],
881 "kai-ambient": [
882 ("init: Kai Engel ambient field — C major, slow morphing pad", "pierre"),
883 ("feat(pad1): first layer — high strings, ppp, infinite sustain", "pierre"),
884 ("feat(pad2): second pad — piano harmonics, prepared technique", "sofia"),
885 ("feat(piano): sparse piano melody — whole notes, bars 9-24", "pierre"),
886 ("feat(v1): v1 branch — original release version, 8-minute version", "pierre"),
887 ("refactor(v1): v1 — master level adjusted to -14 LUFS", "sofia"),
888 ("feat(v2): v2-extended — added 4-minute drone coda", "pierre"),
889 ("feat(v2): v2-extended — new string layer in coda, sul tasto", "sofia"),
890 ("fix(phase): reduce stereo width in pad2 to avoid phase cancellation", "pierre"),
891 ("refactor(mix): filter low end on pad1 — HPF at 80Hz", "pierre"),
892 ],
893 "neo-baroque": [
894 ("init: Neo-Baroque in D Dorian — harpsichord + electric bass at 84 BPM", "gabriel"),
895 ("feat(counterpoint): two-voice invention — right hand melody, left hand bass", "gabriel"),
896 ("feat(jazz): jazz-voicings branch — quartal harmony replaces triads", "gabriel"),
897 ("feat(jazz): tritone substitution in bar 8 turnaround — jazz-voicings", "marcus"),
898 ("feat(jazz): rootless 9th voicings in right hand — jazz-voicings", "gabriel"),
899 ("feat(harmony): harmonic sequence — descending 5ths in bass", "gabriel"),
900 ("feat(rhythm): syncopated baroque rhythm — quarter-note displacement", "gabriel"),
901 ("feat(edm): edm-bassline branch — 808 sub bass under baroque melody", "marcus"),
902 ("feat(edm): four-on-the-floor kick added — edm-bassline", "gabriel"),
903 ("feat(edm): filter sweep into bridge — edm-bassline", "marcus"),
904 ("feat(harpsichord): feature/add-harpsichord — harpsichord replaces piano", "gabriel"),
905 ("feat(harpsichord): double manual technique — feature/add-harpsichord", "pierre"),
906 ("fix(voice-leading): parallel 5ths in bar 5 inner voices corrected", "gabriel"),
907 ("refactor(form): add da capo repeat — bars 1-8 return at end", "gabriel"),
908 ("feat(improv): jazz improvisation section — 8 bars over baroque changes", "marcus"),
909 ("fix(timing): realign baroque ornaments to 16th grid", "gabriel"),
910 ("feat(strings): add pizzicato baroque strings — bars 17-32", "sofia"),
911 ("refactor(harmony): rewrite cadence — Phrygian half cadence, bar 16", "gabriel"),
912 ("feat(coda): extended coda with fugal stretto — all voices", "gabriel"),
913 ("fix(harpsichord): velocity normalization — harpsichord lacks dynamics", "gabriel"),
914 ("feat(modal): modal interchange — borrow from D minor in bridge", "marcus"),
915 ("refactor(mix): balance harpsichord vs bass — HF boost on harpsichord", "gabriel"),
916 ("feat(ornament): mordent on beat 1 of each 4-bar phrase", "pierre"),
917 ("feat(jazz2): jazz-voicings v2 — add upper-structure triads", "gabriel"),
918 ("refactor(form): restructure to AABBA form — stronger contrast", "gabriel"),
919 ("feat(bass): walking bass line for jazz-voicings bridge section", "marcus"),
920 ("fix(modal): correct Dorian vs natural minor in bar 12", "gabriel"),
921 ("feat(fugue): mini fugue in coda — 3-voice, 8 bars", "gabriel"),
922 ],
923 "jazz-chopin": [
924 ("init: Jazz Chopin — Op.9 No.2 reharmonized, Bb minor at 68 BPM", "aaliya"),
925 ("feat(reharmonize): tritone sub on V7 chord — bar 4", "aaliya"),
926 ("feat(reharmonize): minor ii-V-I substitution in bridge", "gabriel"),
927 ("feat(voicing): rootless 9th chord voicings — left hand", "aaliya"),
928 ("feat(reharmonize): Coltrane substitution pattern in climax — reharmonized", "aaliya"),
929 ("feat(reharmonize): add chromatic approach chords — reharmonized", "marcus"),
930 ("feat(trio): trio-arrangement — add bass and drums", "aaliya"),
931 ("feat(trio): walking bass added under reharmonized changes", "marcus"),
932 ("feat(trio): brushed snare — light jazz feel, trio-arrangement", "gabriel"),
933 ("fix(voice-leading): parallel 5ths in reharmonized bridge, bar 9", "aaliya"),
934 ("refactor(melody): add bebop ornaments to Chopin melody line", "aaliya"),
935 ("feat(reharmonize): backdoor ii-V substitution in outro", "aaliya"),
936 ("fix(bass): fix intonation issue on low Bb — walking bass", "marcus"),
937 ("feat(trio): piano solo chorus over jazz changes — trio-arrangement", "aaliya"),
938 ("refactor(tempo): add ritardando at 4-bar phrase ends", "aaliya"),
939 ("feat(reharmonize): modal interchange — iv chord from parallel minor", "gabriel"),
940 ("fix(drums): remove unintentional kick on beat 3 — trio", "aaliya"),
941 ("feat(coda): free improvisation coda — all three voices", "aaliya"),
942 ("refactor(harmony): ensure all substitutions maintain melodic identity", "aaliya"),
943 ("feat(reharmonize): full reharmonized version complete — all 3 sections", "aaliya"),
944 ],
945 "ragtime-edm": [
946 ("init: Ragtime EDM — Maple Leaf Rag MIDI over trap beat at 128 BPM", "marcus"),
947 ("feat(trap): trap hi-hat grid — 16th triplets with velocity variation", "marcus"),
948 ("feat(trap): 808 kick on 1 and 3 — trap-version", "marcus"),
949 ("feat(trap): snare on 2 and 4 with ghosted 16ths — trap-version", "gabriel"),
950 ("feat(ragtime): ragtime melody quantized to EDM grid — bars 1-16", "marcus"),
951 ("feat(house): house-version — 4-on-floor kick, Chicago-style", "marcus"),
952 ("feat(house): sidechain compression on ragtime bass — house-version", "gabriel"),
953 ("feat(house): filter sweep on ragtime melody — house-version", "marcus"),
954 ("feat(swing): electro-swing branch — shuffle quantize 16ths to swing", "marcus"),
955 ("feat(swing): brass sample layer on ragtime melody — electro-swing", "gabriel"),
956 ("fix(pitch): transpose ragtime melody up one semitone to Ab for EDM mix", "marcus"),
957 ("refactor(mix): sidechain bass to kick for pumping effect — all versions", "marcus"),
958 ("feat(drop): big drop transition — silence then tutti return", "marcus"),
959 ("fix(timing): tighten ragtime melody to EDM grid — was 10ms behind", "gabriel"),
960 ("refactor(master): normalize to -8 LUFS for streaming platforms", "marcus"),
961 ("feat(bridge): 8-bar bridge — minimal, just kick and ragtime melody fragment", "marcus"),
962 ("feat(outro): outro — gradual filter close on all elements", "marcus"),
963 ],
964 "film-score": [
965 ("init: Film Score — Act I, C (31-TET) — establishing motif, tense, pp", "chen"),
966 ("feat(act1): act1 — microtonal string cluster, bars 1-8", "chen"),
967 ("feat(act1): act1 — ascending quarter-tone figure in winds", "chen"),
968 ("feat(act1): act1 — timpani accent on beat 3 — instability motif", "gabriel"),
969 ("feat(act1): act1 — brass pedal — low brass drone, bars 9-16", "chen"),
970 ("feat(act2): act2 branch — spectral climax, full orchestra tutti", "chen"),
971 ("feat(act2): act2 — strings in high register, ff, sul ponticello", "sofia"),
972 ("feat(act2): act2 — timpani rolls and brass fanfare — climax peak", "chen"),
973 ("feat(act2): act2 — dissonant chord cluster, all 31-TET pitches", "gabriel"),
974 ("feat(act3): act3 branch — resolution, return to simple C major", "chen"),
975 ("feat(act3): act3 — solo violin melody, simple diatonic, pp", "sofia"),
976 ("feat(act3): act3 — gradual orchestral return from silence", "chen"),
977 ("feat(act3): act3 — final chord — C major, fff, held for 8 bars", "gabriel"),
978 ("fix(tuning): recalibrate all instruments to 31-TET in Act I", "chen"),
979 ("refactor(dynamics): smooth transition from Act I pp to Act II ff", "chen"),
980 ("fix(voice-leading): remove dissonance clash in Act III resolution", "sofia"),
981 ("refactor(score): add rehearsal letter marks every 8 bars", "chen"),
982 ("feat(leitmotif): recurring motif appears in each act — unifying thread", "gabriel"),
983 ],
984 "polyrhythm": [
985 ("init: Polyrhythm Studies — A minor at 92 BPM — 7-over-4 base", "fatou"),
986 ("feat(7-4): 7-over-4 — djembe in 7, talking drum in 4", "fatou"),
987 ("feat(7-4): 7-over-4 — bass guitar anchors common pulse", "fatou"),
988 ("feat(7-4): 7-over-4 — listener orientation — hi-hat on beat 1 only", "aaliya"),
989 ("refactor(7-4): humanize djembe timing — ±8ms variance", "fatou"),
990 ("feat(5-3): 5-over-3-experiment — conga in 5, shekere in 3", "fatou"),
991 ("feat(5-3): 5-over-3-experiment — bass anchors on shared beat", "aaliya"),
992 ("feat(5-3): 5-over-3-experiment — add melody on shared downbeats only", "fatou"),
993 ("fix(phase): fix drifting phase in 7-over-4 at bar 32 — MIDI timing", "fatou"),
994 ("feat(groove): add cross-stick snare to bridge the two rhythmic worlds", "aaliya"),
995 ("refactor(mix): bring up djembe attack — was buried under bass", "fatou"),
996 ],
997 "community": [
998 ("init: Community Collab — open canvas — C major, 90 BPM", "gabriel"),
999 ("feat(counterpoint): sofia's counterpoint — Bach-inspired 2-voice invention", "sofia"),
1000 ("feat(ornament): yuki's ornaments — granular delay on all voices", "yuki"),
1001 ("feat(analysis): pierre's analysis annotations — harmonic function labels", "pierre"),
1002 ("feat(bass): marcus's bassline — funk groove under baroque counterpoint", "marcus"),
1003 ("feat(rhythm): fatou's polyrhythm layer — 5-over-3 pattern over 4/4", "fatou"),
1004 ("feat(reharmonize): aaliya's jazz reharmonization of C major progression", "aaliya"),
1005 ("feat(microtonal): chen's microtonal ornaments — quarter-tone glissandi", "chen"),
1006 ("refactor(structure): gabriel rebalances all layers — new mix", "gabriel"),
1007 ("feat(baroque): sofia adds fugal episode — subject and answer", "sofia"),
1008 ("feat(texture): yuki adds granular texture layer — sparse grain scatter", "yuki"),
1009 ("fix(voice-leading): pierre fixes parallel 5ths — bars 12-13", "pierre"),
1010 ("feat(groove): marcus adds clavinet stabs — funk energy", "marcus"),
1011 ("feat(perc): fatou adds shekere pulse — holds everything together", "fatou"),
1012 ("feat(jazz): aaliya adds blue notes to melody — jazzy feel", "aaliya"),
1013 ("feat(tuning): chen corrects micro-tuning in ornament layer", "chen"),
1014 ("feat(improv): gabriel improvises bridge over new changes", "gabriel"),
1015 ("feat(strings): sofia adds lush string pad — bars 33-48", "sofia"),
1016 ("feat(reverb): yuki adds cathedral reverb to string layer", "yuki"),
1017 ("feat(harmony): pierre adds 9th and 11th extensions to all chords", "pierre"),
1018 ("feat(bass2): marcus doubles bassline at octave — thicker low end", "marcus"),
1019 ("feat(perc2): fatou adds djembe solo — bars 49-56", "fatou"),
1020 ("feat(modal): aaliya introduces Dorian mode shift in bridge", "aaliya"),
1021 ("feat(spectral): chen adds spectral filter sweep — act of transformation", "chen"),
1022 ("feat(motif): gabriel introduces 4-note motif — appears in all layers", "gabriel"),
1023 ("refactor(mix): sofia adjusts balance — counterpoint more prominent", "sofia"),
1024 ("feat(scatter): yuki reduces grain density for introspective section", "yuki"),
1025 ("feat(chorale): pierre writes 4-voice chorale climax — bars 57-64", "pierre"),
1026 ("feat(solo): marcus piano solo over baroque changes", "marcus"),
1027 ("feat(perc3): fatou polyrhythm climax — all layers simultaneously", "fatou"),
1028 ("feat(reprise): aaliya leads reprise of opening theme — reharmonized", "aaliya"),
1029 ("feat(finale): chen's finale motif — microtonal glissando into last chord", "chen"),
1030 ("feat(coda): gabriel's coda — reduces to solo piano, pp", "gabriel"),
1031 ("refactor(final-mix): sofia final mix pass — all dynamics balanced", "sofia"),
1032 ("feat(outro): yuki granular outro — voices dissolve into texture", "yuki"),
1033 ("feat(credits): pierre adds annotation — credits all contributors", "pierre"),
1034 ],
1035 # Archive expansion — batch-14
1036 "moonlight": [
1037 ("init: Moonlight Sonata — C# minor Op. 27 No. 2 — Mvt. I Adagio sostenuto", "gabriel"),
1038 ("feat(mvt1): triplet arpeggio figure — LH, bars 1-4, pp sempre", "gabriel"),
1039 ("feat(mvt1): melody enters RH — single notes above arpeggios, bar 5", "sofia"),
1040 ("feat(mvt1): harmonic turn — diminished 7th, bar 14, sudden tension", "gabriel"),
1041 ("feat(mvt1): climax chord — fff fortissimo, bar 51, stark contrast", "chen"),
1042 ("feat(mvt1): decrescendo return — LH arpeggio reduced, pp ending", "gabriel"),
1043 ("feat(mvt2): Allegretto — Db major, minuet form, delicate trio", "pierre"),
1044 ("feat(mvt2): trio section — darker Bbm inflection, inner voices active", "sofia"),
1045 ("feat(mvt3): Presto agitato — C# minor, sonata form, furious 16ths", "gabriel"),
1046 ("feat(mvt3): development — harmonic sequence, turbulent modulations", "gabriel"),
1047 ("feat(mvt3): recapitulation — original theme returns, fff dynamic", "chen"),
1048 ("fix(mvt1): correct sustain pedal placement — bars 1-3 full pedal", "gabriel"),
1049 ("refactor(mvt1): add hairpin dynamics per Urtext edition", "sofia"),
1050 ("fix(mvt3): missing grace note in bar 163 — add 16th-note ornament", "gabriel"),
1051 ],
1052 "beethoven-sonatas": [
1053 ("init: Op. 2 No. 1 in F minor — Allegro, Adagio, Menuetto, Prestissimo", "gabriel"),
1054 ("feat(op2-2): Op. 2 No. 2 in A major — brilliant Allegro vivace", "sofia"),
1055 ("feat(op2-3): Op. 2 No. 3 in C major — orchestral ambition in miniature", "gabriel"),
1056 ("feat(op7): Op. 7 in Eb major — Grande Sonate, noble and expansive", "gabriel"),
1057 ("feat(op10-3): Op. 10 No. 3 in D major — profound Largo e mesto", "pierre"),
1058 ("feat(op13): Op. 13 Pathétique in C minor — Grave introduction, urgency", "gabriel"),
1059 ("feat(op13): Pathétique Adagio cantabile — one of the most loved themes", "sofia"),
1060 ("feat(op14): Op. 14 No. 1 in E major — Allegro moderato, charming", "gabriel"),
1061 ("feat(op26): Op. 26 in Ab major — theme and variations, funeral march", "chen"),
1062 ("feat(op27-1): Op. 27 No. 1 quasi una fantasia — unusual form", "gabriel"),
1063 ("feat(op27-2): Op. 27 No. 2 Moonlight — branch mvt1-adagio", "gabriel"),
1064 ("feat(op31-2): Op. 31 No. 2 Tempest in D minor — stormy Allegro", "gabriel"),
1065 ("feat(op53): Op. 53 Waldstein in C major — blazing virtuosity", "marcus"),
1066 ("feat(op57): Op. 57 Appassionata in F minor — fiercely dramatic", "gabriel"),
1067 ("feat(op81a): Op. 81a Les Adieux — farewell, absence, return", "pierre"),
1068 ("feat(op90): Op. 90 in E minor — two movements, introspective late style", "gabriel"),
1069 ("feat(op101): Op. 101 in A major — early late period, lyrical", "sofia"),
1070 ("feat(op106): Op. 106 Hammerklavier in Bb — immense, demanding", "gabriel"),
1071 ("feat(op109): Op. 109 in E major — theme and variations, sublimity", "gabriel"),
1072 ("feat(op110): Op. 110 in Ab major — fugue and arioso alternation", "chen"),
1073 ("feat(op111): Op. 111 in C minor — two movements, final word", "gabriel"),
1074 ("refactor(all): normalize dynamics across all 32 sonatas — LUFS check", "sofia"),
1075 ],
1076 "mozart-k331": [
1077 ("init: K. 331 Sonata in A major — theme Andante grazioso, 6/8", "gabriel"),
1078 ("feat(theme): 8-bar theme — A major, simple melody, ornamented reprise", "gabriel"),
1079 ("feat(var1): Variation 1 — 16th-note figuration in RH over theme", "sofia"),
1080 ("feat(var2): Variation 2 — LH broken octaves, minor inflections", "gabriel"),
1081 ("feat(var3): Variation 3 — minor mode, A minor, darker character", "pierre"),
1082 ("feat(var4): Variation 4 — Adagio, deeply ornamented, almost improvised", "gabriel"),
1083 ("feat(var5): Variation 5 — Allegro, energetic 16th triplets throughout", "sofia"),
1084 ("feat(var6): Variation 6 — Alla Turca mood, dotted rhythms, ornate", "gabriel"),
1085 ("feat(menuetto): Menuetto in A major — elegant, binary form — track:piano", "gabriel"),
1086 ("feat(menuetto): Trio section — slight minor inflection, contrasting", "pierre"),
1087 ("feat(turkish-march): Rondo alla Turca in A minor — the famous theme", "gabriel"),
1088 ("feat(turkish-march): B section — moves to A major, brilliant passagework", "sofia"),
1089 ("feat(turkish-march): C section — full tutti effect, A major, march rhythms", "gabriel"),
1090 ("fix(var4): correct ornament resolution — trill should end on principal note", "gabriel"),
1091 ("refactor(theme): add dynamic markings per Mozart autograph", "pierre"),
1092 ],
1093 "suite-bergamasque": [
1094 ("init: Suite Bergamasque — Prélude in F major, Andante con moto", "gabriel"),
1095 ("feat(prelude): Prélude — driving 8th notes, parallel motion, F major", "gabriel"),
1096 ("feat(prelude): Prélude — harmonic sequence descending by 5ths, bars 13-20", "sofia"),
1097 ("feat(menuet): Menuet in F major — graceful, archaic modal inflections", "pierre"),
1098 ("feat(menuet): Menuet trio — D minor, slightly darker character", "gabriel"),
1099 ("feat(clair): Clair de Lune — Db major, Andante espressivo", "sofia"),
1100 ("feat(clair): Clair de Lune — arpeggiated LH fills entire texture, bars 1-8", "gabriel"),
1101 ("feat(clair): Clair de Lune — climax fff, bars 51-56, full resonance", "gabriel"),
1102 ("feat(clair): Clair de Lune — diminuendo, returning to ppp opening mood", "pierre"),
1103 ("feat(clair): Clair de Lune — final bars — pppp, dissolving into silence", "sofia"),
1104 ("feat(passepied): Passepied — F major, playful, dance-like 3/8", "gabriel"),
1105 ("feat(passepied): Passepied — modal inflections, Dorian tinge in episode", "sofia"),
1106 ("fix(clair): correct pedaling in bars 15-16 — was over-pedaled, blurring", "gabriel"),
1107 ("refactor(clair): add tempo rubato markings — Debussy's own annotations", "sofia"),
1108 ],
1109 "gymnopedie": [
1110 ("init: Gymnopédie No. 1 — D major, Lent et douloureux, 3/4", "pierre"),
1111 ("feat(no1): chord pattern — D major, then G major, alternating bars 1-4", "pierre"),
1112 ("feat(no1): melody — gentle, floating above chords, sparse ornament", "sofia"),
1113 ("feat(no1): inner voices — subtle 5th in LH, no bass movement", "pierre"),
1114 ("feat(no1): restatement — melody repeats, slight variation, pp to ppp", "pierre"),
1115 ("feat(no2): Gymnopédie No. 2 — C major, Lent et triste, 3/4", "pierre"),
1116 ("feat(no2): No. 2 melody — more active than No. 1, rising contour", "sofia"),
1117 ("feat(no2): No. 2 harmony — IV-I, no dominant motion — Aeolian quality", "gabriel"),
1118 ("feat(no3): Gymnopédie No. 3 — G major, Lent et grave, 3/4", "pierre"),
1119 ("feat(no3): No. 3 melody — lowest register of the three, somber", "pierre"),
1120 ("feat(no3): No. 3 ending — sustained chord, decrescendo, pure silence", "gabriel"),
1121 ("fix(no1): correct tempo — Lent is 60 BPM maximum, was set too fast", "pierre"),
1122 ("refactor(all): unified dynamics — all three end pppp, connected by silence", "pierre"),
1123 ],
1124 "impromptus": [
1125 ("init: Impromptus Op. 90 — D. 899 — No. 1 in C minor, Allegro molto", "gabriel"),
1126 ("feat(no1): opening theme — C minor, stormy repeated-note figure", "gabriel"),
1127 ("feat(no1): contrasting theme — Eb major, lyrical cantabile", "sofia"),
1128 ("feat(no1): development — modulates through remote keys, C minor return", "gabriel"),
1129 ("feat(no2): Op. 90 No. 2 in Eb major — Allegro, perpetual motion 16ths", "gabriel"),
1130 ("feat(no2): No. 2 trio — Bb minor, more contemplative, slower feel", "pierre"),
1131 ("feat(no2): No. 2 return — Eb major with ornamental additions", "gabriel"),
1132 ("feat(no3): Op. 90 No. 3 in Gb major — Andante, song without words style", "sofia"),
1133 ("feat(no3): No. 3 middle section — arpeggiated texture replaces melody", "gabriel"),
1134 ("feat(no3): No. 3 return — melody richly ornamented, Gb major radiant", "sofia"),
1135 ("feat(no4): Op. 90 No. 4 in Ab major — Allegretto, Hungarian dance feeling", "gabriel"),
1136 ("feat(no4): No. 4 — Ab major, inner voices create countermelody", "pierre"),
1137 ("fix(no1): correct ornament type — turn vs mordent at bar 5, beat 3", "gabriel"),
1138 ("refactor(all): apply Schubertian dynamics — sfz accents, long diminuendo", "sofia"),
1139 ],
1140 # Cross-composer remix repos — batch-15
1141 "well-tempered-rag": [
1142 ("init: Well-Tempered Rag — forked from bach/goldberg-variations, G major", "gabriel"),
1143 ("feat(source): source-bach branch — original Goldberg Aria, untouched", "gabriel"),
1144 ("feat(bass): Bach continuo bass line isolated as standalone track", "gabriel"),
1145 ("feat(syncopation): add Joplin-style syncopation to Bach RH melody", "gabriel"),
1146 ("feat(oom-pah): replace Bach LH arpeggios with Joplin oom-pah bass", "marcus"),
1147 ("feat(harmony): keep Bach chord progression, add ragtime rhythmic treatment", "gabriel"),
1148 ("feat(melody): Bach melodic ornaments replaced with Joplin grace-note style", "gabriel"),
1149 ("feat(sections): Joplin A-B-C-D section structure mapped to Bach form", "marcus"),
1150 ("refactor(bass): tighten oom-pah timing — 16th-note precision", "gabriel"),
1151 ("feat(experiment): swing-feel branch — shuffle quantize Bach to Joplin swing", "gabriel"),
1152 ("fix(harmony): Goldberg Var 13 ornaments clashed with ragtime oom-pah", "gabriel"),
1153 ("feat(bridge): 8-bar ragtime bridge — original composition connecting eras", "marcus"),
1154 ("refactor(mix): balance baroque texture with ragtime momentum", "gabriel"),
1155 ],
1156 "nocturne-variations": [
1157 ("init: Nocturne Variations — forked from chopin/nocturnes, C# minor", "sofia"),
1158 ("feat(source-chopin): source-chopin branch — Nocturne No. 20 Posthumous", "sofia"),
1159 ("feat(source-satie): source-satie branch — Gymnopédie No. 1, transposed", "sofia"),
1160 ("feat(analysis): compare melodic contour — both begin with upward 3rd", "sofia"),
1161 ("feat(merge): merge commit — Chopin RH melody over Satie LH chord pattern", "sofia"),
1162 ("feat(harmony): Satie's parallel chords under Chopin's ornate RH melody", "gabriel"),
1163 ("feat(tempo): unified tempo — both at 54 BPM, rubato preserved", "sofia"),
1164 ("feat(texture): Chopin's RH ornaments enrich Satie's bare texture", "pierre"),
1165 ("refactor(merge): resolve voice-leading conflict at measure 9", "sofia"),
1166 ("fix(pitch): Satie transposed to C# minor — Chopin's key", "sofia"),
1167 ("feat(outro): fade to Satie's characteristic bare chord — ppp", "pierre"),
1168 ],
1169 "moonlight-jazz": [
1170 ("init: Moonlight Jazz — forked from beethoven/moonlight-sonata, C# minor", "marcus"),
1171 ("feat(source): source-beethoven branch — Mvt. I Adagio, untouched", "marcus"),
1172 ("feat(comp): replace triplet arpeggios with jazz piano comping pattern", "marcus"),
1173 ("feat(harmony): tritone substitution on V7 — G# dominant → D7", "marcus"),
1174 ("feat(voicing): rootless 9th voicings replace Beethoven's octave doublings", "marcus"),
1175 ("feat(bass): walking bass line added under reharmonized changes", "aaliya"),
1176 ("feat(bass): bass counter-melody in development section", "aaliya"),
1177 ("feat(experiment): trio-arrangement branch — add brushed drums", "gabriel"),
1178 ("feat(drums): brushed snare pattern — triplet jazz feel over 4/4", "gabriel"),
1179 ("feat(harmony): Coltrane substitution in climax section — tritone chains", "marcus"),
1180 ("fix(voice-leading): parallel 5ths in reharmonized bar 9 — corrected", "marcus"),
1181 ("refactor(tempo): maintain Beethoven's Adagio, add jazz rubato phrasing", "marcus"),
1182 ("feat(solo): piano improvisation coda over jazz changes — 8 bars", "marcus"),
1183 ],
1184 "clair-deconstructed": [
1185 ("init: Clair de Lune Deconstructed — forked from debussy/suite-bergamasque", "yuki"),
1186 ("feat(source): source-debussy branch — Clair de Lune, untouched", "yuki"),
1187 ("feat(grain): granular decomposition of arpeggiated LH — grain size 40ms", "yuki"),
1188 ("feat(pad): Db major harmony becomes sustaining pad from grain scatter", "yuki"),
1189 ("feat(texture): RH melody extracted, stretched to 4x duration — pad layer", "yuki"),
1190 ("feat(filter): low-pass filter sweeps as Debussy's dynamic moves pppp to fff", "yuki"),
1191 ("feat(reverb): 12-second reverb tail — blurs phrase boundaries", "yuki"),
1192 ("feat(experiment): drone-branch — single Db pedal as foundation", "sofia"),
1193 ("feat(drone): layering harmonics 1-8 over Db drone — spectral approach", "sofia"),
1194 ("fix(grain): reduce grain density in climax — too dense at fff moment", "yuki"),
1195 ("refactor(tempo): rubato map from Debussy annotation — applied to grains", "yuki"),
1196 ],
1197 "ragtime-afrobeat": [
1198 ("init: Ragtime Afrobeat — forked from scott_joplin/maple-leaf-rag, Ab major", "aaliya"),
1199 ("feat(source): source-joplin branch — Maple Leaf Rag, untouched", "aaliya"),
1200 ("feat(rhythm): add West African 12/8 percussion layer under 4/4 melody", "aaliya"),
1201 ("feat(drums): talking drum in 3 against Joplin's syncopated 4", "fatou"),
1202 ("feat(bass): shekere pulse on every 8th of 12/8 — locks with oom-pah", "fatou"),
1203 ("feat(melody): Joplin's RH melody preserved — cross-rhythm makes it swing", "aaliya"),
1204 ("feat(harmony): add Yoruba call-and-response horn answering melody", "aaliya"),
1205 ("feat(experiment): 12-8 branch — full rewrite in 12/8 time signature", "fatou"),
1206 ("feat(12-8): melody adapted to 12/8 — original syncopation becomes triplet", "aaliya"),
1207 ("refactor(mix): balance afrobeat percussion against ragtime piano level", "aaliya"),
1208 ("fix(timing): djembe phase drift at bar 32 — realign to 8th grid", "fatou"),
1209 ("feat(outro): gradual percussion fade while Joplin melody remains", "aaliya"),
1210 ],
1211 }
1212
1213 key = repo_key
1214 templates = TEMPLATES.get(key, TEMPLATES["neo-soul"])
1215 commits: list[dict[str, Any]] = []
1216 prev_id: str | None = None
1217 branch = "main"
1218 t = templates * ((n // len(templates)) + 1)
1219
1220 for i in range(n):
1221 cid = _sha(f"{repo_id}-commit-{i}")
1222 msg = t[i % len(templates)][0]
1223 author = t[i % len(templates)][1]
1224 days = (n - i) * 2 # older commits further back
1225 commits.append(dict(
1226 commit_id=cid,
1227 repo_id=repo_id,
1228 branch=branch,
1229 parent_ids=[prev_id] if prev_id else [],
1230 message=msg,
1231 author=author,
1232 timestamp=_now(days=days),
1233 snapshot_id=_sha(f"snap-{repo_id}-{i}"),
1234 ))
1235 prev_id = cid
1236 # Sprinkle in a feature branch every ~8 commits
1237 if i > 0 and i % 8 == 0:
1238 branch = "main"
1239
1240 return commits
1241
1242
1243 # Track roles per repo key for the instrument breakdown bar
1244 REPO_TRACKS: dict[str, list[tuple[str, str]]] = {
1245 "neo-soul": [("bass", "tracks/bass.mid"), ("keys", "tracks/rhodes.mid"),
1246 ("drums", "tracks/drums.mid"), ("strings", "tracks/strings.mid"),
1247 ("horns", "tracks/trumpet.mid"), ("horns", "tracks/alto_sax.mid"),
1248 ("guitar", "tracks/guitar.mid"), ("vocals", "tracks/vocals.mid")],
1249 "modal-jazz": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"),
1250 ("drums", "tracks/drums.mid"), ("trumpet", "tracks/trumpet.mid"),
1251 ("guitar", "tracks/guitar.mid")],
1252 "ambient": [("pad", "tracks/pad.mid"), ("arp", "tracks/arpeggiator.mid"),
1253 ("strings", "tracks/strings.mid"), ("bells", "tracks/bells.mid"),
1254 ("drone", "tracks/drone.mid")],
1255 "afrobeat": [("perc", "tracks/talking_drum.mid"), ("guitar", "tracks/guitar.mid"),
1256 ("bass", "tracks/bass.mid"), ("horns", "tracks/horns.mid"),
1257 ("keys", "tracks/rhodes.mid"), ("perc", "tracks/shekere.mid"),
1258 ("vocals", "tracks/vocals.mid")],
1259 "microtonal": [("piano", "tracks/piano.mid"), ("strings", "tracks/strings.mid"),
1260 ("woodwinds", "tracks/woodwinds.mid"), ("perc", "tracks/percussion.mid")],
1261 "drums": [("kick", "tracks/kick.mid"), ("snare", "tracks/snare.mid"),
1262 ("hihat", "tracks/hihat.mid"), ("perc", "tracks/djembe.mid"),
1263 ("808", "tracks/808.mid")],
1264 "chanson": [("piano", "tracks/piano.mid"), ("cello", "tracks/cello.mid")],
1265 "granular": [("pad", "tracks/granular_pad.mid"), ("texture", "tracks/texture.mid"),
1266 ("rhythm", "tracks/rhythmic.mid")],
1267 "funk-suite": [("bass", "tracks/bass.mid"), ("keys", "tracks/electric_piano.mid"),
1268 ("clavinet", "tracks/clavinet.mid"), ("drums", "tracks/drums.mid"),
1269 ("guitar", "tracks/guitar.mid"), ("horns", "tracks/horns.mid"),
1270 ("perc", "tracks/congas.mid")],
1271 "jazz-trio": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"),
1272 ("drums", "tracks/drums.mid")],
1273 # Genre archive repos — batch-13
1274 "wtc": [("piano", "tracks/piano.mid"), ("harpsichord", "tracks/harpsichord.mid")],
1275 "goldberg": [("piano", "tracks/piano.mid"), ("harpsichord", "tracks/harpsichord.mid")],
1276 "nocturnes": [("piano", "tracks/piano.mid")],
1277 "maple-leaf": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid")],
1278 "cinematic-strings":[("violin1", "tracks/violin1.mid"), ("violin2", "tracks/violin2.mid"),
1279 ("viola", "tracks/viola.mid"), ("cello", "tracks/cello.mid"),
1280 ("bass", "tracks/double_bass.mid"), ("timp", "tracks/timpani.mid")],
1281 "kai-ambient": [("pad", "tracks/pad.mid"), ("piano", "tracks/piano.mid"),
1282 ("strings", "tracks/strings.mid")],
1283 "neo-baroque": [("harpsichord", "tracks/harpsichord.mid"), ("bass", "tracks/bass.mid"),
1284 ("strings", "tracks/strings.mid")],
1285 "jazz-chopin": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"),
1286 ("drums", "tracks/drums.mid")],
1287 "ragtime-edm": [("piano", "tracks/piano.mid"), ("kick", "tracks/kick.mid"),
1288 ("snare", "tracks/snare.mid"), ("hihat", "tracks/hihat.mid"),
1289 ("808", "tracks/808.mid")],
1290 "film-score": [("strings", "tracks/strings.mid"), ("brass", "tracks/brass.mid"),
1291 ("woodwinds", "tracks/woodwinds.mid"), ("timp", "tracks/timpani.mid")],
1292 "polyrhythm": [("djembe", "tracks/djembe.mid"), ("tama", "tracks/talking_drum.mid"),
1293 ("shekere", "tracks/shekere.mid"), ("bass", "tracks/bass.mid")],
1294 "community": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"),
1295 ("strings", "tracks/strings.mid"), ("perc", "tracks/djembe.mid"),
1296 ("harpsichord", "tracks/harpsichord.mid"), ("pad", "tracks/granular_pad.mid")],
1297 # Archive expansion — batch-14
1298 "moonlight": [("piano", "tracks/piano.mid")],
1299 "beethoven-sonatas":[("piano", "tracks/piano.mid")],
1300 "mozart-k331": [("piano", "tracks/piano.mid")],
1301 "suite-bergamasque":[("piano", "tracks/piano.mid")],
1302 "gymnopedie": [("piano", "tracks/piano.mid")],
1303 "impromptus": [("piano", "tracks/piano.mid")],
1304 # Cross-composer remix repos — batch-15
1305 "well-tempered-rag": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid")],
1306 "nocturne-variations":[("piano", "tracks/piano.mid"), ("pad", "tracks/pad.mid")],
1307 "moonlight-jazz": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"),
1308 ("drums", "tracks/drums.mid")],
1309 "clair-deconstructed":[("pad", "tracks/granular_pad.mid"), ("piano", "tracks/piano.mid"),
1310 ("texture", "tracks/texture.mid")],
1311 "ragtime-afrobeat": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"),
1312 ("perc", "tracks/djembe.mid"), ("perc", "tracks/talking_drum.mid")],
1313 }
1314
1315 REPO_KEY_MAP = {
1316 REPO_NEO_SOUL: "neo-soul",
1317 REPO_MODAL_JAZZ: "modal-jazz",
1318 REPO_AMBIENT: "ambient",
1319 REPO_AFROBEAT: "afrobeat",
1320 REPO_MICROTONAL: "microtonal",
1321 REPO_DRUM_MACHINE: "drums",
1322 REPO_CHANSON: "chanson",
1323 REPO_GRANULAR: "granular",
1324 REPO_FUNK_SUITE: "funk-suite",
1325 REPO_JAZZ_TRIO: "jazz-trio",
1326 REPO_NEO_SOUL_FORK: "neo-soul",
1327 REPO_AMBIENT_FORK: "ambient",
1328 # Genre archive repos — batch-13
1329 REPO_WTC: "wtc",
1330 REPO_GOLDBERG: "goldberg",
1331 REPO_NOCTURNES: "nocturnes",
1332 REPO_MAPLE_LEAF: "maple-leaf",
1333 REPO_CIN_STRINGS: "cinematic-strings",
1334 REPO_KAI_AMBIENT: "kai-ambient",
1335 REPO_NEO_BAROQUE: "neo-baroque",
1336 REPO_JAZZ_CHOPIN: "jazz-chopin",
1337 REPO_RAGTIME_EDM: "ragtime-edm",
1338 REPO_FILM_SCORE: "film-score",
1339 REPO_POLYRHYTHM: "polyrhythm",
1340 REPO_COMMUNITY: "community",
1341 # Archive expansion — batch-14
1342 REPO_MOONLIGHT: "moonlight",
1343 REPO_BEETHOVEN_SONATAS: "beethoven-sonatas",
1344 REPO_MOZART_K331: "mozart-k331",
1345 REPO_SUITE_BERGAMASQUE: "suite-bergamasque",
1346 REPO_GYMNOPEDIE: "gymnopedie",
1347 REPO_IMPROMPTUS: "impromptus",
1348 # Cross-composer remix repos — batch-15
1349 REPO_WELL_TEMPERED_RAG: "well-tempered-rag",
1350 REPO_NOCTURNE_VARIATIONS: "nocturne-variations",
1351 REPO_MOONLIGHT_JAZZ: "moonlight-jazz",
1352 REPO_CLAIR_DECONSTRUCTED: "clair-deconstructed",
1353 REPO_RAGTIME_AFROBEAT: "ragtime-afrobeat",
1354 }
1355
1356 COMMIT_COUNTS = {
1357 REPO_NEO_SOUL: 40,
1358 REPO_MODAL_JAZZ: 30,
1359 REPO_AMBIENT: 35,
1360 REPO_AFROBEAT: 38,
1361 REPO_MICROTONAL: 25,
1362 REPO_DRUM_MACHINE: 28,
1363 REPO_CHANSON: 22,
1364 REPO_GRANULAR: 24,
1365 REPO_FUNK_SUITE: 42,
1366 REPO_JAZZ_TRIO: 32,
1367 REPO_NEO_SOUL_FORK: 8,
1368 REPO_AMBIENT_FORK: 5,
1369 # Genre archive repos — batch-13
1370 REPO_WTC: 60,
1371 REPO_GOLDBERG: 35,
1372 REPO_NOCTURNES: 45,
1373 REPO_MAPLE_LEAF: 25,
1374 REPO_CIN_STRINGS: 30,
1375 REPO_KAI_AMBIENT: 20,
1376 REPO_NEO_BAROQUE: 55,
1377 REPO_JAZZ_CHOPIN: 40,
1378 REPO_RAGTIME_EDM: 35,
1379 REPO_FILM_SCORE: 28,
1380 REPO_POLYRHYTHM: 22,
1381 REPO_COMMUNITY: 70,
1382 # Archive expansion — batch-14
1383 REPO_MOONLIGHT: 30,
1384 REPO_BEETHOVEN_SONATAS: 55,
1385 REPO_MOZART_K331: 28,
1386 REPO_SUITE_BERGAMASQUE: 25,
1387 REPO_GYMNOPEDIE: 18,
1388 REPO_IMPROMPTUS: 22,
1389 # Cross-composer remix repos — batch-15
1390 REPO_WELL_TEMPERED_RAG: 25,
1391 REPO_NOCTURNE_VARIATIONS: 20,
1392 REPO_MOONLIGHT_JAZZ: 22,
1393 REPO_CLAIR_DECONSTRUCTED: 18,
1394 REPO_RAGTIME_AFROBEAT: 24,
1395 }
1396
1397 # Specific branch configurations for genre archive repos (batch-13).
1398 # Each entry: list of (branch_name, commit_offset_from_end) — offset 0 = HEAD.
1399 GENRE_REPO_BRANCHES: dict[str, list[tuple[str, int]]] = {
1400 REPO_WTC: [("prelude-bk1", 50), ("fugue-bk1", 42), ("prelude-bk2", 20), ("fugue-bk2", 10)],
1401 REPO_GOLDBERG: [("aria-only", 30), ("variation-13-experimental", 15)],
1402 REPO_NOCTURNES: [("op9", 38), ("op15", 25), ("op27", 12)],
1403 REPO_MAPLE_LEAF: [("slow-version", 10), ("marcus-edm-remix", 5)],
1404 REPO_CIN_STRINGS: [("orchestral", 20), ("stripped-piano", 8)],
1405 REPO_KAI_AMBIENT: [("v1", 14), ("v2-extended", 6)],
1406 REPO_NEO_BAROQUE: [("experiment/jazz-voicings", 45), ("experiment/edm-bassline", 30), ("feature/add-harpsichord", 15)],
1407 REPO_JAZZ_CHOPIN: [("reharmonized", 30), ("trio-arrangement", 15)],
1408 REPO_RAGTIME_EDM: [("trap-version", 28), ("house-version", 18), ("electro-swing", 8)],
1409 REPO_FILM_SCORE: [("act1", 22), ("act2", 14), ("act3", 6)],
1410 REPO_POLYRHYTHM: [("7-over-4", 16), ("5-over-3-experiment", 8)],
1411 REPO_COMMUNITY: [("sofias-counterpoint", 60), ("yukis-ornaments", 50), ("pierres-analysis", 40), ("marcuss-bassline", 25)],
1412 # Archive expansion — batch-14
1413 REPO_MOONLIGHT: [("mvt1-adagio", 25), ("mvt2-allegretto", 15), ("mvt3-presto", 5)],
1414 REPO_BEETHOVEN_SONATAS: [("op2-early-sonatas", 50), ("op27-moonlight", 35), ("op53-waldstein", 20), ("op111-final", 5)],
1415 REPO_MOZART_K331: [("theme", 24), ("variations", 15), ("menuetto", 8), ("turkish-march", 2)],
1416 REPO_SUITE_BERGAMASQUE: [("prelude", 20), ("clair-de-lune", 12), ("menuet", 6), ("passepied", 2)],
1417 REPO_GYMNOPEDIE: [("no1", 14), ("no2", 8), ("no3", 3)],
1418 REPO_IMPROMPTUS: [("op90-no1-cm", 18), ("op90-no2-eb", 12), ("op90-no3-gb", 7), ("op90-no4-ab", 2)],
1419 # Cross-composer remix repos — batch-15
1420 REPO_WELL_TEMPERED_RAG: [("source-bach", 22), ("main-remix", 12), ("experiment/swing-feel", 5)],
1421 REPO_NOCTURNE_VARIATIONS: [("source-chopin", 18), ("source-satie", 12), ("main-merged", 5)],
1422 REPO_MOONLIGHT_JAZZ: [("source-beethoven", 18), ("main-jazz", 10), ("experiment/trio", 4)],
1423 REPO_CLAIR_DECONSTRUCTED: [("source-debussy", 15), ("main-granular", 8), ("experiment/drone", 3)],
1424 REPO_RAGTIME_AFROBEAT: [("source-joplin", 20), ("main-fusion", 12), ("experiment/12-8", 5)],
1425 }
1426
1427 # ---------------------------------------------------------------------------
1428 # Muse VCS — content-addressed MIDI objects, snapshots, commits, tags
1429 # ---------------------------------------------------------------------------
1430
1431 # Track files per repo for Muse VCS — realistic MIDI instrument names and sizes.
1432 # Piano solo: 8KB–40KB; ensemble: 50KB–200KB (task spec).
1433 # Each tuple is (filename, base_size_bytes).
1434 MUSE_VCS_FILES: dict[str, list[tuple[str, int]]] = {
1435 REPO_NEO_SOUL: [("piano.mid", 24576), ("bass.mid", 12288), ("drums.mid", 16384),
1436 ("violin.mid", 18432), ("trumpet.mid", 13312)],
1437 REPO_FUNK_SUITE: [("piano.mid", 22528), ("bass.mid", 13312), ("drums.mid", 16384),
1438 ("trumpet.mid", 12288), ("flute.mid", 10240)],
1439 REPO_AFROBEAT: [("bass.mid", 14336), ("drums.mid", 18432), ("violin.mid", 15360),
1440 ("cello.mid", 14336), ("trumpet.mid", 12288)],
1441 REPO_AMBIENT: [("piano.mid", 32768), ("violin.mid", 20480), ("cello.mid", 17408),
1442 ("viola.mid", 15360), ("flute.mid", 11264)],
1443 REPO_MODAL_JAZZ: [("piano.mid", 28672), ("bass.mid", 10240), ("drums.mid", 14336),
1444 ("trumpet.mid", 11264)],
1445 REPO_JAZZ_TRIO: [("piano.mid", 26624), ("bass.mid", 11264), ("drums.mid", 13312)],
1446 REPO_MICROTONAL: [("piano.mid", 20480), ("violin.mid", 16384), ("cello.mid", 14336)],
1447 REPO_DRUM_MACHINE: [("drums.mid", 18432), ("bass.mid", 12288)],
1448 REPO_CHANSON: [("piano.mid", 36864), ("cello.mid", 17408)],
1449 REPO_GRANULAR: [("piano.mid", 15360), ("violin.mid", 12288), ("flute.mid", 9216)],
1450 REPO_NEO_SOUL_FORK:[("piano.mid", 24576), ("bass.mid", 12288), ("drums.mid", 16384)],
1451 REPO_AMBIENT_FORK: [("piano.mid", 32768), ("violin.mid", 20480), ("cello.mid", 17408)],
1452 # Archive expansion — batch-14 (all solo piano)
1453 REPO_MOONLIGHT: [("piano.mid", 38912)],
1454 REPO_BEETHOVEN_SONATAS: [("piano.mid", 40960)],
1455 REPO_MOZART_K331: [("piano.mid", 28672)],
1456 REPO_SUITE_BERGAMASQUE: [("piano.mid", 35840)],
1457 REPO_GYMNOPEDIE: [("piano.mid", 18432)],
1458 REPO_IMPROMPTUS: [("piano.mid", 30720)],
1459 # Cross-composer remix repos — batch-15
1460 REPO_WELL_TEMPERED_RAG: [("piano.mid", 28672), ("bass.mid", 12288)],
1461 REPO_NOCTURNE_VARIATIONS: [("piano.mid", 32768), ("pad.mid", 14336)],
1462 REPO_MOONLIGHT_JAZZ: [("piano.mid", 30720), ("bass.mid", 11264), ("drums.mid", 13312)],
1463 REPO_CLAIR_DECONSTRUCTED: [("granular_pad.mid", 20480), ("piano.mid", 24576), ("texture.mid", 16384)],
1464 REPO_RAGTIME_AFROBEAT: [("piano.mid", 26624), ("bass.mid", 12288), ("djembe.mid", 10240), ("talking_drum.mid", 9216)],
1465 }
1466
1467 # Metadata per repo for muse_commits.metadata JSON field.
1468 MUSE_COMMIT_META: dict[str, dict[str, object]] = {
1469 REPO_NEO_SOUL: {"tempo_bpm": 92.0, "key": "F# minor", "time_signature": "4/4", "instrument_count": 5},
1470 REPO_FUNK_SUITE: {"tempo_bpm": 108.0, "key": "E minor", "time_signature": "4/4", "instrument_count": 5},
1471 REPO_AFROBEAT: {"tempo_bpm": 128.0, "key": "G major", "time_signature": "12/8","instrument_count": 5},
1472 REPO_AMBIENT: {"tempo_bpm": 60.0, "key": "Eb major", "time_signature": "4/4", "instrument_count": 5},
1473 REPO_MODAL_JAZZ: {"tempo_bpm": 120.0, "key": "D Dorian", "time_signature": "4/4", "instrument_count": 4},
1474 REPO_JAZZ_TRIO: {"tempo_bpm": 138.0, "key": "Bb major", "time_signature": "3/4", "instrument_count": 3},
1475 REPO_MICROTONAL: {"tempo_bpm": 76.0, "key": "C (31-TET)","time_signature":"4/4", "instrument_count": 3},
1476 REPO_DRUM_MACHINE: {"tempo_bpm": 100.0, "key": "A minor", "time_signature": "4/4", "instrument_count": 2},
1477 REPO_CHANSON: {"tempo_bpm": 52.0, "key": "A major", "time_signature": "4/4", "instrument_count": 2},
1478 REPO_GRANULAR: {"tempo_bpm": 70.0, "key": "E minor", "time_signature": "4/4", "instrument_count": 3},
1479 REPO_NEO_SOUL_FORK:{"tempo_bpm": 92.0, "key": "F# minor", "time_signature": "4/4", "instrument_count": 3},
1480 REPO_AMBIENT_FORK: {"tempo_bpm": 60.0, "key": "Eb major", "time_signature": "4/4", "instrument_count": 3},
1481 # Archive expansion — batch-14
1482 REPO_MOONLIGHT: {"tempo_bpm": 54.0, "key": "C# minor", "time_signature": "4/4", "instrument_count": 1},
1483 REPO_BEETHOVEN_SONATAS: {"tempo_bpm": 108.0, "key": "Various", "time_signature": "4/4", "instrument_count": 1},
1484 REPO_MOZART_K331: {"tempo_bpm": 104.0, "key": "A major", "time_signature": "6/8", "instrument_count": 1},
1485 REPO_SUITE_BERGAMASQUE: {"tempo_bpm": 54.0, "key": "Db major", "time_signature": "4/4", "instrument_count": 1},
1486 REPO_GYMNOPEDIE: {"tempo_bpm": 52.0, "key": "D major", "time_signature": "3/4", "instrument_count": 1},
1487 REPO_IMPROMPTUS: {"tempo_bpm": 62.0, "key": "C minor", "time_signature": "4/4", "instrument_count": 1},
1488 # Cross-composer remix repos — batch-15
1489 REPO_WELL_TEMPERED_RAG: {"tempo_bpm": 96.0, "key": "G major", "time_signature": "4/4", "instrument_count": 2},
1490 REPO_NOCTURNE_VARIATIONS: {"tempo_bpm": 54.0, "key": "C# minor", "time_signature": "4/4", "instrument_count": 2},
1491 REPO_MOONLIGHT_JAZZ: {"tempo_bpm": 60.0, "key": "C# minor", "time_signature": "4/4", "instrument_count": 3},
1492 REPO_CLAIR_DECONSTRUCTED: {"tempo_bpm": 50.0, "key": "Db major", "time_signature": "4/4", "instrument_count": 3},
1493 REPO_RAGTIME_AFROBEAT: {"tempo_bpm": 112.0, "key": "Ab major", "time_signature": "12/8","instrument_count": 4},
1494 }
1495
1496 # Muse tag taxonomy — ALL values from the task spec must appear in the seed.
1497 MUSE_EMOTION_TAGS = [
1498 "melancholic", "joyful", "tense", "serene", "triumphant",
1499 "mysterious", "playful", "tender", "energetic", "complex",
1500 ]
1501 MUSE_STAGE_TAGS = [
1502 "sketch", "rough-mix", "arrangement", "production", "mixing", "mastering", "released",
1503 ]
1504 MUSE_KEY_TAGS = [
1505 "C", "Am", "G", "Em", "Bb", "F#", "Db", "Abm", "D", "Bm", "A", "F", "Eb", "Cm",
1506 ]
1507 MUSE_TEMPO_TAGS = [
1508 "60bpm", "72bpm", "80bpm", "96bpm", "120bpm", "132bpm", "140bpm", "160bpm",
1509 ]
1510 MUSE_GENRE_TAGS = [
1511 "baroque", "romantic", "ragtime", "edm", "ambient", "cinematic",
1512 "jazz", "afrobeats", "classical", "fusion",
1513 ]
1514 MUSE_REF_TAGS = [
1515 "bach", "chopin", "debussy", "coltrane", "daft-punk", "beethoven", "joplin", "monk",
1516 ]
1517
1518 # Full flat list of all taxonomy tags — used when cycling through commits.
1519 _ALL_MUSE_TAGS: list[str] = (
1520 MUSE_EMOTION_TAGS
1521 + MUSE_STAGE_TAGS
1522 + MUSE_KEY_TAGS
1523 + MUSE_TEMPO_TAGS
1524 + MUSE_GENRE_TAGS
1525 + MUSE_REF_TAGS
1526 )
1527
1528 # Repos that get the full rich tag taxonomy (most active, richest history).
1529 MUSE_RICH_TAG_REPOS = {REPO_NEO_SOUL, REPO_FUNK_SUITE}
1530
1531
1532 # ---------------------------------------------------------------------------
1533 # Issue templates
1534 # ---------------------------------------------------------------------------
1535
1536 ISSUE_TEMPLATES: dict[str, list[dict[str, Any]]] = {
1537 "neo-soul": [
1538 dict(n=1, state="open", title="Bass line loses tension in bar 9",
1539 body="3-against-4 pulse drifts. Ghost note on beat 2.5 recommended.", labels=["groove", "bass"]),
1540 dict(n=2, state="open", title="Add guitar scratch rhythm track",
1541 body="Arrangement too sparse. Scratch guitar would complement Rhodes.", labels=["arrangement"]),
1542 dict(n=3, state="closed", title="Tempo fluctuates bars 4-8",
1543 body="Resolved by re-quantizing with tight humanization.", labels=["tempo", "drums"]),
1544 dict(n=4, state="open", title="Choir voicing too wide in chorus",
1545 body="Soprano and bass parts are 2+ octaves apart — muddy on small speakers.", labels=["harmony"]),
1546 dict(n=5, state="open", title="Organ swell clashes with Rhodes",
1547 body="Both sit in mid-range 400-800Hz. Pan or EQ to separate.", labels=["mix"]),
1548 dict(n=6, state="closed", title="String pizzicato timing off",
1549 body="Fixed — re-quantized to 16th grid with 10ms humanize.", labels=["strings", "timing"]),
1550 dict(n=7, state="open", title="Bridge needs more harmonic tension",
1551 body="The IV-I cadence in the bridge is too resolved. Try IV-bVII.", labels=["harmony", "bridge"]),
1552 dict(n=8, state="open", title="Trumpet counter-melody too high",
1553 body="Goes above high C. Alto sax range would be more idiomatic.", labels=["horns"]),
1554 dict(n=9, state="closed", title="Bass note collision on beat 1",
1555 body="Fixed — root changed from F# to C# (5th) to reduce mud.", labels=["bass", "harmony"]),
1556 dict(n=10, state="open", title="Add breakdown section before final chorus",
1557 body="Energy needs to drop before the big finish. 4-bar bass+drums only.", labels=["arrangement"]),
1558 dict(n=11, state="open", title="Vocals too bright — needs de-essing",
1559 body="Sibilance prominent on headphones. High shelf cut above 10kHz.", labels=["mix", "vocals"]),
1560 dict(n=12, state="open", title="Consider key change to A minor for outro",
1561 body="A modulation to relative major would give a brighter feel at the end.", labels=["harmony"]),
1562 dict(n=13, state="closed", title="Rhodes voicing clashes in bar 12",
1563 body="Fixed — upper structure triad replaced with shell voicing (root + 7th).", labels=["piano", "harmony"]),
1564 dict(n=14, state="open", title="Add shaker for groove density in pre-chorus",
1565 body="The pre-chorus feels lighter than the verse. A 16th-note shaker would tie the pulse together.",
1566 labels=["groove", "perc"]),
1567 dict(n=15, state="open", title="Vocal compression artifacts on sustained notes",
1568 body="Long vowels show pumping at attack. Reduce ratio from 8:1 to 4:1 and increase attack to 10ms.",
1569 labels=["mix", "vocals"]),
1570 ],
1571 "modal-jazz": [
1572 dict(n=1, state="open", title="Phrygian bridge needs ii-V turnaround",
1573 body="Jump from D Dorian to E Phrygian is abrupt. Add Am7b5 → D7alt.", labels=["harmony"]),
1574 dict(n=2, state="open", title="Swing factor inconsistent piano vs bass",
1575 body="Piano at 0.65 swing, bass at 0.55. Should match.", labels=["groove", "timing"]),
1576 dict(n=3, state="closed", title="Piano pedaling too heavy in changes",
1577 body="Fixed — reduced sustain pedal range.", labels=["piano"]),
1578 dict(n=4, state="open", title="Guitar chord stabs too loud",
1579 body="Freddie Green stabs should sit under the piano. Lower -3dB.", labels=["mix", "guitar"]),
1580 dict(n=5, state="open", title="Head melody needs resolution note",
1581 body="The A section ends on 6th scale degree — unresolved. Add scale degree 1.", labels=["melody"]),
1582 dict(n=6, state="open", title="Tritone sub reharmonization too frequent",
1583 body="Using sub every 2 bars sounds formulaic. Reserve for 8-bar phrase end.", labels=["harmony"]),
1584 dict(n=7, state="closed", title="Bass solo too long — loses listener",
1585 body="Trimmed to 16 bars. Better pacing.", labels=["bass"]),
1586 dict(n=8, state="open", title="Drummer needs to lay back on trumpet solo",
1587 body="Ride accent too prominent during solo. Comp more sparsely.", labels=["drums"]),
1588 dict(n=9, state="open", title="Piano comping too busy in A section",
1589 body="Left-hand comp obscures walking bass line. Simplify to 2-feel.", labels=["piano", "arrangement"]),
1590 dict(n=10, state="closed", title="Trumpet range error — written vs concert pitch",
1591 body="Fixed — all trumpet parts transposed down a major 2nd to concert pitch.", labels=["horns"]),
1592 dict(n=11, state="open", title="Add lydian mode variation in B section",
1593 body="The B section stays strictly Dorian. A Lydian passage would add color.", labels=["harmony"]),
1594 dict(n=12, state="open", title="Bass register too low in chorus",
1595 body="Walking bass drops below E1 — inaudible on most systems. Transpose up an octave.", labels=["bass"]),
1596 dict(n=13, state="open", title="Snare ghost notes need velocity curve",
1597 body="All ghosts at velocity 40 — too uniform. Use 20-50 range with slight randomization.", labels=["drums"]),
1598 dict(n=14, state="closed", title="Key center ambiguous in intro",
1599 body="Fixed — added a clear D Dorian vamp at the start before the head.", labels=["harmony"]),
1600 dict(n=15, state="open", title="Outro needs ritardando",
1601 body="The piece ends abruptly at tempo. Gradual slow-down over last 4 bars would give closure.",
1602 labels=["arrangement"]),
1603 ],
1604 "ambient": [
1605 dict(n=1, state="open", title="Arpeggiator repeats — needs more variation",
1606 body="After 32 bars the pattern becomes predictable. Modulate seed every 8 bars.", labels=["generative"]),
1607 dict(n=2, state="open", title="Pad too washy — needs more definition",
1608 body="Attack of 4s is too slow. Try 2s with a short sustain plateau.", labels=["pad"]),
1609 dict(n=3, state="closed", title="Stuck note in arp at bar 64",
1610 body="Fixed — MIDI note-off added. Was a gate issue.", labels=["bug", "midi"]),
1611 dict(n=4, state="open", title="Add harmonic movement after bar 48",
1612 body="The Eb pedal has been static for 3 minutes. Move to Ab for 8 bars.", labels=["harmony"]),
1613 dict(n=5, state="open", title="Norwegian church reverb is too bright",
1614 body="High frequency content in reverb tail is distracting. EQ pre-send.", labels=["mix"]),
1615 dict(n=6, state="open", title="Granular density too high in intro",
1616 body="Start sparser and build. Currently too dense from bar 1.", labels=["texture"]),
1617 dict(n=7, state="closed", title="Phase correlation issues in stereo pad",
1618 body="Resolved by setting stereo width to 80% (was 120%).", labels=["mix"]),
1619 dict(n=8, state="open", title="Piano melody needs more dynamic variation",
1620 body="All notes at same velocity. Add cresc/dim on each 4-bar phrase.", labels=["piano", "dynamics"]),
1621 dict(n=9, state="open", title="Wind chimes pitched too high",
1622 body="7th partial sits above 8kHz on most speakers. Lower source pitch.", labels=["texture"]),
1623 dict(n=10, state="open", title="Generative seed produces repeated rhythmic clusters",
1624 body="Seed 42 has a bias toward beat 1 and 3. Rotate seed every 16 bars.",
1625 labels=["generative", "bug"]),
1626 dict(n=11, state="closed", title="Cello sustain too long — blurs transitions",
1627 body="Fixed — reduced release to 2s from 6s. Now transitions are audible.", labels=["strings"]),
1628 dict(n=12, state="open", title="Add breath sounds between sections",
1629 body="Silence between sections is too abrupt. A subtle room tone or breath sample would ease transitions.",
1630 labels=["texture", "arrangement"]),
1631 dict(n=13, state="open", title="LFO rate too fast on pad filter",
1632 body="0.1Hz LFO creates audible tremolo. Slow to 0.02Hz for imperceptible movement.",
1633 labels=["pad", "generative"]),
1634 dict(n=14, state="open", title="Mono bass under stereo pad causes phase issues",
1635 body="Bass is mono center, pad is 120° wide. Below 200Hz the combination cancels. HPF pad below 250Hz.",
1636 labels=["mix"]),
1637 dict(n=15, state="closed", title="Arp note lengths too uniform",
1638 body="Fixed — gate time now varies from 50% to 90% per note.", labels=["generative"]),
1639 ],
1640 "afrobeat": [
1641 dict(n=1, state="open", title="Talking drum pattern needs more swing",
1642 body="Djembe is perfectly quantized — needs human timing ±5ms.", labels=["groove", "perc"]),
1643 dict(n=2, state="open", title="Highlife guitar pattern clash with bass",
1644 body="Both emphasise beat 1. Guitar should accent beats 2 and 4.", labels=["arrangement"]),
1645 dict(n=3, state="closed", title="Conga timing drift at bar 32",
1646 body="Fixed — re-quantized to 8th note grid.", labels=["perc", "timing"]),
1647 dict(n=4, state="open", title="Brass unison too thick — needs harmony",
1648 body="Four instruments in unison is thin. Split into 3-part harmony.", labels=["horns"]),
1649 dict(n=5, state="open", title="Vocal call-and-response timing off",
1650 body="Response phrases enter 1 beat early. Needs 4-beat gap.", labels=["vocals"]),
1651 dict(n=6, state="open", title="Add agogo bell pattern",
1652 body="The timeline/bell pattern is missing. Essential for afrobeat structure.", labels=["perc"]),
1653 dict(n=7, state="open", title="Bass slap too clicky at high velocity",
1654 body="Velocities above 100 produce unwanted transient click.", labels=["bass"]),
1655 dict(n=8, state="closed", title="Organ swell level too high",
1656 body="Reduced by -4dB. Now sits correctly behind guitar.", labels=["mix"]),
1657 dict(n=9, state="open", title="Yoruba lyric timing — stress on wrong syllable",
1658 body="Need input from native speaker on placement of tonal accent.", labels=["vocals", "cultural"]),
1659 dict(n=10, state="open", title="Add Talking Heads-style guitar texture",
1660 body="Open-string plucked guitar arpeggio on top of the rhythm section.", labels=["guitar"]),
1661 dict(n=11, state="open", title="Shekere part clashes with hi-hat",
1662 body="Both playing 16th pattern in the same register. Pan shekere hard right, hi-hat left.",
1663 labels=["perc", "mix"]),
1664 dict(n=12, state="closed", title="Bass register too muddy below 80Hz",
1665 body="Fixed — high-pass filter at 60Hz with 6dB/oct slope applied.", labels=["mix", "bass"]),
1666 dict(n=13, state="open", title="Trumpet solo needs call-and-response with guitar",
1667 body="Current solo is solo instrument only. Adding guitar responses every 2 bars would honor the tradition.",
1668 labels=["horns", "guitar", "arrangement"]),
1669 dict(n=14, state="open", title="Polyrhythm section needs tempo anchor",
1670 body="The 3-over-2 polyrhythm section lacks a clear pulse anchor. A kick on beat 1 every bar would help.",
1671 labels=["groove", "perc"]),
1672 dict(n=15, state="closed", title="Intro too long — listener disengages",
1673 body="Fixed — trimmed from 16 bars to 8 bars. Groove now enters at bar 9.", labels=["arrangement"]),
1674 ],
1675 "microtonal": [ # REPO_KEY_MAP key: "microtonal"
1676 dict(n=1, state="open", title="31-TET tuning table not loading on export",
1677 body="MIDI export falls back to 12-TET. Need to embed the tuning table in SysEx.", labels=["bug", "midi"]),
1678 dict(n=2, state="open", title="Neutral third interval sounds jarring in context",
1679 body="The 11/9 neutral third in bar 7 needs a resolving phrase. It hangs unresolved.", labels=["harmony"]),
1680 dict(n=3, state="closed", title="Playback pitch drift after bar 48",
1681 body="Fixed — DAW clock sync issue. Resolved by enabling MIDI clock.", labels=["bug"]),
1682 dict(n=4, state="open", title="Add justly-tuned overtone drone",
1683 body="A drone on the 5th partial (5/4 above root) would anchor the spectral harmony.", labels=["texture", "harmony"]),
1684 dict(n=5, state="open", title="Spectral voice leading too disjunct",
1685 body="Leaps of more than 7 steps in 31-TET feel chromatic. Stepwise motion preferred.", labels=["melody"]),
1686 dict(n=6, state="open", title="Cello bow speed inconsistency",
1687 body="Bow speed changes mid-phrase cause unintended dynamics. Normalize velocity curve.", labels=["strings"]),
1688 dict(n=7, state="closed", title="Score notation doesn't reflect microtonal accidentals",
1689 body="Fixed — using Helmholtz-Ellis notation for all quarter-tones.", labels=["notation"]),
1690 dict(n=8, state="open", title="Overtone series segment 8-16 missing",
1691 body="Partials 8-16 not included in the harmonic texture. Add soft flute tones for those partials.",
1692 labels=["harmony", "texture"]),
1693 dict(n=9, state="open", title="Attack transients too sharp in 31-TET scale runs",
1694 body="Fast runs in 31-TET sound percussive. Soften attack to 20ms.", labels=["dynamics"]),
1695 dict(n=10, state="closed", title="Tuning reference pitch wrong",
1696 body="Fixed — set A=432Hz as agreed for this piece.", labels=["tuning"]),
1697 dict(n=11, state="open", title="Add quarter-tone trill in cadential passage",
1698 body="The cadence (bars 22-24) lacks ornament. A quarter-tone trill on the leading tone would help.",
1699 labels=["melody", "ornament"]),
1700 dict(n=12, state="open", title="Sustain pedal creates pitch smear in 31-TET",
1701 body="Held notes at different 31-TET pitches ring together creating beating. Reduce pedal depth.",
1702 labels=["piano", "tuning"]),
1703 dict(n=13, state="open", title="Section 3 needs dynamic arc",
1704 body="Section 3 stays at mf throughout. Build from pp to ff over 16 bars.", labels=["dynamics"]),
1705 dict(n=14, state="closed", title="MIDI velocity map doesn't match 31-TET dynamics",
1706 body="Fixed — remapped velocity curve to match the intended dynamic nuance.", labels=["midi"]),
1707 dict(n=15, state="open", title="Missing rest in bar 19 causes overlap",
1708 body="Violin and cello overlap by one beat in bar 19. Insert an 8th rest.", labels=["notation", "bug"]),
1709 ],
1710 "drums": [ # REPO_KEY_MAP key: "drums" (REPO_DRUM_MACHINE)
1711 dict(n=1, state="open", title="808 kick too short — needs longer decay",
1712 body="Kick envelope decay at 0.1s sounds punchy but loses sub presence. Try 0.4s.", labels=["808", "drums"]),
1713 dict(n=2, state="open", title="Hi-hat pattern too rigid — needs humanize",
1714 body="All hats at 16th grid. Add ±8ms timing offset and velocity 60-90 range.", labels=["groove", "drums"]),
1715 dict(n=3, state="closed", title="Clap reverb tail too long",
1716 body="Fixed — reduced reverb to 0.8s. Clap now sits in the groove.", labels=["mix"]),
1717 dict(n=4, state="open", title="Add polyrhythmic hi-hat ostinato",
1718 body="Current pattern is 4/4 grid. Add a 3-against-4 hi-hat line as a layer.", labels=["groove", "drums"]),
1719 dict(n=5, state="open", title="Modular snare too bright at 4kHz",
1720 body="High transient spike at 4kHz sounds harsh. EQ notch at 4kHz, -4dB, Q=2.", labels=["mix", "drums"]),
1721 dict(n=6, state="closed", title="Kick/bass frequency masking",
1722 body="Fixed — sidechain compression on bass triggered by kick.", labels=["mix"]),
1723 dict(n=7, state="open", title="Pattern variation needed at bar 17",
1724 body="The pattern repeats unmodified for 16 bars. Add a fill at bar 17.", labels=["arrangement", "drums"]),
1725 dict(n=8, state="open", title="808 tuning: needs pitch envelope",
1726 body="Kick pitch stays flat. A fast downward pitch sweep (1 octave, 50ms) would be more musical.",
1727 labels=["808"]),
1728 dict(n=9, state="open", title="Tom fills too frequent",
1729 body="Tom fills every 4 bars interrupt the groove flow. Reduce to every 8 bars.", labels=["drums"]),
1730 dict(n=10, state="closed", title="Crash cymbal sample too long",
1731 body="Fixed — trimmed to 2s with fade-out.", labels=["drums", "mix"]),
1732 dict(n=11, state="open", title="Polyrhythm section: 5-against-4 too abrupt",
1733 body="The shift to 5-against-4 at bar 33 needs 2 bars of transition.", labels=["groove", "arrangement"]),
1734 dict(n=12, state="open", title="Missing ghost note pattern in verse",
1735 body="Verse section lacks ghost notes — pattern sounds flat. Add 16th ghosts at velocity 25-35.",
1736 labels=["drums", "groove"]),
1737 dict(n=13, state="closed", title="Open hi-hat not choked by closed hat",
1738 body="Fixed — added hat-choke controller message at each closed hat hit.", labels=["drums", "midi"]),
1739 dict(n=14, state="open", title="Rimshot too loud relative to snare",
1740 body="Rimshot peaks 3dB above snare. Level down or use snare for fills.", labels=["mix", "drums"]),
1741 dict(n=15, state="open", title="Add shaker for 16th-note pulse reference",
1742 body="The groove loses its feel at slower tempo passages. A shaker pulse would anchor the listener.",
1743 labels=["perc", "groove"]),
1744 ],
1745 "chanson": [
1746 dict(n=1, state="open", title="Piano left hand too busy in verse",
1747 body="Alberti bass pattern is too active for chanson miniature style. Try sparse block chords.",
1748 labels=["piano", "arrangement"]),
1749 dict(n=2, state="open", title="Cello pizzicato needs more resonance",
1750 body="Pizzicato sounds thin at 52 BPM. Add short room reverb to give note length.",
1751 labels=["strings"]),
1752 dict(n=3, state="closed", title="Piano sustain pedal creates blur in slow passage",
1753 body="Fixed — split pedaling technique applied to maintain harmonic clarity.", labels=["piano"]),
1754 dict(n=4, state="open", title="Melody too narrow — stays in A4-E5 range",
1755 body="Expand downward to A3. The lower register gives a more intimate chanson character.",
1756 labels=["melody"]),
1757 dict(n=5, state="open", title="Final cadence needs ritardando",
1758 body="The piece ends metrically. A gradual slow-down over 2 bars would give weight to the ending.",
1759 labels=["arrangement", "dynamics"]),
1760 dict(n=6, state="open", title="Add optional accordion doubling",
1761 body="Chanson tradition supports musette accordion. A soft doubling of the piano melody would be idiomatic.",
1762 labels=["arrangement"]),
1763 dict(n=7, state="closed", title="Cello bowing direction markers missing",
1764 body="Fixed — down-bows on beats 1 and 3, up-bows on 2 and 4.", labels=["strings", "notation"]),
1765 dict(n=8, state="open", title="Bridge modulation to C# minor too abrupt",
1766 body="The pivot chord (E major = shared dominant) should be held for 2 bars before modulating.",
1767 labels=["harmony"]),
1768 dict(n=9, state="open", title="Silence sections too short",
1769 body="Pierre's style uses 4-bar silences. Current rests are only 2 bars — double them.",
1770 labels=["arrangement", "dynamics"]),
1771 dict(n=10, state="closed", title="Notation: accidentals not consistent",
1772 body="Fixed — standardized to sharps throughout (A major context).", labels=["notation"]),
1773 dict(n=11, state="open", title="Piano voicing too wide in left hand",
1774 body="Bass notes below C2 sound muddy on a grand piano. Raise left hand by an octave.",
1775 labels=["piano"]),
1776 dict(n=12, state="open", title="Add pedal marking for the coda",
1777 body="Coda (bars 28-32) has no pedal indication. The color should be hazy — add una corda.",
1778 labels=["piano", "dynamics"]),
1779 dict(n=13, state="open", title="Tempo too fast for lyric melancholy",
1780 body="52 BPM feels hurried for this material. Try 44 BPM — aligns with Pierre's reference recordings.",
1781 labels=["arrangement"]),
1782 dict(n=14, state="closed", title="Cello enters too early in bar 5",
1783 body="Fixed — shifted cello entrance to bar 6 beat 1.", labels=["strings", "timing"]),
1784 dict(n=15, state="open", title="Middle section lacks harmonic tension",
1785 body="The A major tonality is too stable for 8 bars. Introduce a borrowed chord (mode mixture) at bar 20.",
1786 labels=["harmony"]),
1787 ],
1788 "granular": [
1789 dict(n=1, state="open", title="Grain density parameter too uniform",
1790 body="30 grains/sec is constant throughout. Modulate between 5 and 80 for organic feel.",
1791 labels=["generative", "texture"]),
1792 dict(n=2, state="open", title="Source sample quality too clean",
1793 body="Found sounds should be degraded. Add vinyl noise and room tone before granulating.",
1794 labels=["texture"]),
1795 dict(n=3, state="closed", title="Grain size too small — produces clicks",
1796 body="Fixed — minimum grain size set to 40ms (was 5ms). Click-free.", labels=["bug", "generative"]),
1797 dict(n=4, state="open", title="Pitch randomization range too wide",
1798 body="±1 octave pitch spread sounds noisy, not musical. Constrain to ±major 3rd.",
1799 labels=["generative", "pitch"]),
1800 dict(n=5, state="open", title="Add grain position automation over time",
1801 body="Reading from fixed position 0.3 in the source. Automate position 0.0→1.0 over 4 minutes.",
1802 labels=["generative"]),
1803 dict(n=6, state="closed", title="Stereo pan spread too narrow",
1804 body="Fixed — grain pan randomization set to ±45° (was ±10°).", labels=["mix"]),
1805 dict(n=7, state="open", title="Texture layer too loud vs piano layer",
1806 body="Granular texture sits 6dB above the piano melody. Attenuate texture by -6dB.", labels=["mix"]),
1807 dict(n=8, state="open", title="Add resonant filter sweep through granular cloud",
1808 body="A slow bandpass filter sweep (0.05Hz) through the grain cloud would create hypnotic movement.",
1809 labels=["texture", "generative"]),
1810 dict(n=9, state="open", title="Grain envelope too flat — no transients",
1811 body="All grains use linear envelope. Add a fast attack (2ms) + slow decay for percussion-like texture.",
1812 labels=["generative"]),
1813 dict(n=10, state="closed", title="Found sound source too recognizable",
1814 body="Fixed — source pitch-shifted and time-stretched until original is unrecognizable.", labels=["texture"]),
1815 dict(n=11, state="open", title="Feedback loop creates unwanted oscillation",
1816 body="Grain output fed back into input causes 12Hz oscillation at high density. Add DC blocker.",
1817 labels=["bug", "generative"]),
1818 dict(n=12, state="open", title="Density automation ramp too abrupt",
1819 body="The jump from 5 to 80 grains/sec happens over 1 bar. Needs 4-bar ramp for smooth transition.",
1820 labels=["generative", "arrangement"]),
1821 dict(n=13, state="closed", title="Grain position quantized to beat grid",
1822 body="Fixed — position now continuous with subtle clock jitter (±10ms).", labels=["generative"]),
1823 dict(n=14, state="open", title="Violin source needs more bow noise character",
1824 body="Current sample is too pure. A sul ponticello bowing noise layer would add grit.", labels=["texture"]),
1825 dict(n=15, state="open", title="Outro needs silence interruption",
1826 body="The granular outro should be punctuated by 500ms silences every 8 bars — gaps in the cloud.",
1827 labels=["arrangement", "texture"]),
1828 ],
1829 "funk-suite": [ # REPO_KEY_MAP key: "funk-suite" (REPO_FUNK_SUITE)
1830 dict(n=1, state="open", title="Electric piano comping too dense in verse",
1831 body="Clavinet and Rhodes both comp simultaneously. Pick one per section.", labels=["arrangement"]),
1832 dict(n=2, state="open", title="Wah bass envelope too slow",
1833 body="Wah envelope follows ADSR but attack is 80ms — loses the click. Set to 10ms.", labels=["bass"]),
1834 dict(n=3, state="closed", title="Hi-hat and shaker doubling causes flamming",
1835 body="Fixed — hi-hat quantized to 16th grid, shaker humanized separately.", labels=["drums", "timing"]),
1836 dict(n=4, state="open", title="Horns need staccato articulation in bars 9-16",
1837 body="Horn stabs are held too long. 16th-note staccato would give the funk punch.", labels=["horns"]),
1838 dict(n=5, state="open", title="Clavinet tone too trebly",
1839 body="Clavinet without a low-pass filter sounds harsh. A gentle 3kHz shelf would smooth it.",
1840 labels=["mix"]),
1841 dict(n=6, state="closed", title="Groove falls apart at bar 25",
1842 body="Fixed — kick pattern re-programmed with 16th anticipation on beat 3.", labels=["groove", "drums"]),
1843 dict(n=7, state="open", title="Movement IV needs a climactic peak",
1844 body="Movement IV builds but never peaks before the final cadence. Add a unison hit at bar 48.",
1845 labels=["arrangement", "dynamics"]),
1846 dict(n=8, state="open", title="Bass slap velocity too uniform",
1847 body="All slap notes at velocity 110. Alternate strong (120) and weak (90) for dynamic groove.",
1848 labels=["bass", "groove"]),
1849 dict(n=9, state="open", title="Pocket drum fills too predictable at phrase ends",
1850 body="Fill every 4 bars, always a snare run. Vary: sometimes a kick + tom, sometimes silence.",
1851 labels=["drums", "arrangement"]),
1852 dict(n=10, state="closed", title="Chord voicings too thick in bridge",
1853 body="Fixed — reduced to 3-voice drop-2 voicing in brass section.", labels=["harmony"]),
1854 dict(n=11, state="open", title="Add octave unison in horn section for climax",
1855 body="The climax in Movement III (bar 56) lacks weight. Add trombones an octave below trumpets.",
1856 labels=["horns", "arrangement"]),
1857 dict(n=12, state="open", title="Movement II transition too abrupt",
1858 body="Movement I ends, Movement II starts without transition. Add 2-bar breakdown.",
1859 labels=["arrangement"]),
1860 dict(n=13, state="closed", title="Clavinet out of tune with Rhodes",
1861 body="Fixed — both set to A=440 reference. Clavinet detuned by +12 cents.", labels=["tuning"]),
1862 dict(n=14, state="open", title="Shaker too loud in mix",
1863 body="Shaker sits 4dB above hi-hat in the mid-range. Reduce shaker -4dB.", labels=["mix"]),
1864 dict(n=15, state="open", title="Final movement needs a proper ending",
1865 body="Suite ends with a fade-out which is too passive. Write a 4-bar coda with a unison hit.",
1866 labels=["arrangement"]),
1867 ],
1868 "jazz-trio": [ # REPO_KEY_MAP key: "jazz-trio" (REPO_JAZZ_TRIO)
1869 dict(n=1, state="open", title="Piano left hand too busy during bass solo",
1870 body="Left-hand comp fills every bar during the bass solo — player needs space.", labels=["piano", "arrangement"]),
1871 dict(n=2, state="open", title="Brushed snare too loud in A section",
1872 body="Brushes are competing with piano in the same frequency range. Reduce snare by -3dB.",
1873 labels=["drums", "mix"]),
1874 dict(n=3, state="closed", title="Walking bass accidentally doubles piano left hand",
1875 body="Fixed — bass transposed up a 10th in bars 5-8 to avoid doubling.", labels=["bass"]),
1876 dict(n=4, state="open", title="Tempo rushes during piano solo",
1877 body="Drummer accelerates during piano solo — common problem. Add a click track reference.",
1878 labels=["tempo", "groove"]),
1879 dict(n=5, state="open", title="Add ritardando at end of each chorus",
1880 body="Each 32-bar chorus ends metrically. A slight rit in the last 2 bars would honor the standard.",
1881 labels=["arrangement", "dynamics"]),
1882 dict(n=6, state="closed", title="Bass pizzicato too short — sounds staccato",
1883 body="Fixed — gate time extended to 90% of note duration.", labels=["bass"]),
1884 dict(n=7, state="open", title="Cymbal swell missing at the top of each chorus",
1885 body="A ride cymbal swell on beat 4 of bar 32 would signal the chorus repeat elegantly.",
1886 labels=["drums", "arrangement"]),
1887 dict(n=8, state="open", title="Piano voicing too sparse in outer choruses",
1888 body="Shell voicings (root + 7th only) are too thin in the outer A sections. Add 3rds.",
1889 labels=["piano", "harmony"]),
1890 dict(n=9, state="open", title="Standards melody not centered in mix",
1891 body="Piano melody sits behind the rhythm section. Bump piano +2dB during head.", labels=["mix"]),
1892 dict(n=10, state="closed", title="Bass note durations overlap chord changes",
1893 body="Fixed — note-offs now aligned to beat boundaries before chord changes.", labels=["bass"]),
1894 dict(n=11, state="open", title="Add sus chord before final turnaround",
1895 body="The final turnaround (bars 29-32) lacks a sus chord to build tension before the resolution.",
1896 labels=["harmony"]),
1897 dict(n=12, state="open", title="Ride cymbal bell too prominent",
1898 body="Bell accent on beat 2 and 4 cuts through the texture. Use shoulder of stick instead.",
1899 labels=["drums"]),
1900 dict(n=13, state="closed", title="Piano octaves in outro too thick",
1901 body="Fixed — reduced to single melody line in the final 4 bars.", labels=["piano"]),
1902 dict(n=14, state="open", title="Tag repeat needs extra bar",
1903 body="Standard convention adds 1 extra bar at the final tag. Currently not present.",
1904 labels=["arrangement"]),
1905 dict(n=15, state="open", title="Double-time feel section needs hi-hat switch",
1906 body="During double-time feel (bars 17-24), hi-hat should move to 2-beat pattern. Currently stays on 4.",
1907 labels=["drums", "groove"]),
1908 ],
1909 }
1910
1911 # Use a generic template for repos without specific issue templates (fork repos)
1912 GENERIC_ISSUES = [
1913 dict(n=1, state="open", title="Energy drops in the middle section",
1914 body="The arrangement loses momentum around bar 24-32. Add element to sustain interest.",
1915 labels=["arrangement", "energy"]),
1916 dict(n=2, state="open", title="Dynamics too compressed",
1917 body="The quietest and loudest moments are within 3dB. Needs more dynamic range.",
1918 labels=["mix", "dynamics"]),
1919 dict(n=3, state="closed", title="Tempo inconsistency between sections",
1920 body="Fixed by applying strict quantize to all MIDI.", labels=["timing"]),
1921 dict(n=4, state="open", title="Add a counter-melody",
1922 body="The main melody is unaccompanied for too long. Add a secondary voice.",
1923 labels=["arrangement"]),
1924 dict(n=5, state="open", title="Harmonic rhythm too fast in verse",
1925 body="Chord changes every 2 beats feels rushed. Try 4-beat chord duration.",
1926 labels=["harmony"]),
1927 dict(n=6, state="closed", title="Mix: low end muddy",
1928 body="Resolved — high-pass filter below 80Hz on all non-bass instruments.",
1929 labels=["mix"]),
1930 dict(n=7, state="open", title="Transition between sections too abrupt",
1931 body="The jump from section A to B lacks a linking phrase. Add a 2-bar turnaround.",
1932 labels=["arrangement"]),
1933 dict(n=8, state="open", title="Lead instrument too forward in mix",
1934 body="The melody sits 6dB above the supporting texture. Reduce by 3dB and add subtle delay.",
1935 labels=["mix"]),
1936 dict(n=9, state="closed", title="Reverb tail bleeds into silence",
1937 body="Fixed — reduced reverb pre-delay to 20ms, decay to 1.5s.", labels=["mix"]),
1938 dict(n=10, state="open", title="Add introduction before main theme",
1939 body="The piece starts on the main theme with no setup. A 4-bar intro would establish context.",
1940 labels=["arrangement"]),
1941 dict(n=11, state="open", title="Velocity variation too narrow",
1942 body="All MIDI velocities within 90-110 range. Expand to 60-127 for natural expression.",
1943 labels=["dynamics"]),
1944 dict(n=12, state="open", title="Stereo field too narrow",
1945 body="Mix is mostly center-panned. Pan secondary voices hard left/right for width.",
1946 labels=["mix"]),
1947 dict(n=13, state="closed", title="Quantization too tight — sounds mechanical",
1948 body="Fixed — applied 75% quantize (humanize 25%).", labels=["groove", "timing"]),
1949 dict(n=14, state="open", title="Ending lacks finality",
1950 body="The piece fades out rather than closing with a defined cadence. Write a 2-bar coda.",
1951 labels=["arrangement"]),
1952 dict(n=15, state="open", title="High frequency content harsh on headphones",
1953 body="Content above 8kHz is piercing. A gentle high shelf cut -3dB above 8kHz would help.",
1954 labels=["mix"]),
1955 ]
1956
1957
1958 # ---------------------------------------------------------------------------
1959 # Milestone templates per repo-key — title, description, state, due offset
1960 # ---------------------------------------------------------------------------
1961
1962 MILESTONE_TEMPLATES: dict[str, list[dict[str, Any]]] = {
1963 "neo-soul": [
1964 dict(n=1, title="Album v1.0", state="open",
1965 description="Full release of Neo-Soul Experiment Vol. 1. All tracks mixed and mastered.",
1966 due_days=60),
1967 dict(n=2, title="Mixing Complete", state="closed",
1968 description="All tracks signed off by mixing engineer. Ready for mastering.",
1969 due_days=None),
1970 ],
1971 "modal-jazz": [
1972 dict(n=1, title="Session Complete", state="open",
1973 description="All Modal Jazz Sketches tracks recorded and approved.",
1974 due_days=30),
1975 ],
1976 "ambient": [
1977 dict(n=1, title="Vol. 1 Complete", state="open",
1978 description="All Ambient Textures Vol. 1 compositions finalised.",
1979 due_days=45),
1980 dict(n=2, title="Mastering Done", state="closed",
1981 description="Mastering session at Abbey Road complete.",
1982 due_days=None),
1983 ],
1984 "afrobeat": [
1985 dict(n=1, title="Album Launch", state="open",
1986 description="Afrobeat Grooves album release. All 12 tracks production-ready.",
1987 due_days=30),
1988 dict(n=2, title="v1.0 Recording", state="closed",
1989 description="All live tracking sessions completed.",
1990 due_days=None),
1991 ],
1992 "microtonal": [
1993 dict(n=1, title="Études Complete", state="open",
1994 description="All 10 microtonal études composed, engraved, and recorded.",
1995 due_days=90),
1996 ],
1997 "drums": [
1998 dict(n=1, title="808 Variations v1.0", state="open",
1999 description="All drum variations composed and exported as stems.",
2000 due_days=20),
2001 ],
2002 "chanson": [
2003 dict(n=1, title="Score Publication", state="open",
2004 description="Score submitted to publisher for Chanson Minimale edition.",
2005 due_days=45),
2006 ],
2007 "granular": [
2008 dict(n=1, title="Research Complete", state="open",
2009 description="All granular synthesis research documented and recordings exported.",
2010 due_days=60),
2011 ],
2012 "funk-suite": [
2013 dict(n=1, title="Suite Release", state="open",
2014 description="Funk Suite No. 1 — all four movements completed and sequenced.",
2015 due_days=25),
2016 dict(n=2, title="Mvt. I–II Done", state="closed",
2017 description="Movements I and II approved by the full ensemble.",
2018 due_days=None),
2019 ],
2020 "jazz-trio": [
2021 dict(n=1, title="Album Complete", state="open",
2022 description="Jazz Trio Sessions album — all takes selected and arranged.",
2023 due_days=40),
2024 ],
2025 }
2026
2027 # Issues in each milestone (by issue number n) — controls milestone_id assignment
2028 MILESTONE_ISSUE_ASSIGNMENTS: dict[str, dict[int, list[int]]] = {
2029 # key: repo_key → {milestone_n: [issue_n, ...]}
2030 "neo-soul": {1: [1, 2, 4, 5, 7, 8, 10, 11, 12, 14, 15],
2031 2: [3, 6, 9, 13]},
2032 "modal-jazz": {1: [1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 15]},
2033 "ambient": {1: [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15],
2034 2: [3, 7, 11]},
2035 "afrobeat": {1: [1, 2, 4, 5, 6, 7, 9, 10, 11, 13, 14, 15],
2036 2: [3, 8, 12]},
2037 "microtonal": {1: [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15]},
2038 "drums": {1: [1, 2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15]},
2039 "chanson": {1: [1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15]},
2040 "granular": {1: [1, 2, 4, 5, 8, 9, 10, 12, 14, 15]},
2041 "funk-suite": {1: [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15],
2042 2: [3, 7, 11]},
2043 "jazz-trio": {1: [1, 2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15]},
2044 }
2045
2046
2047 # ---------------------------------------------------------------------------
2048 # Issue comment templates
2049 # ---------------------------------------------------------------------------
2050
2051 ISSUE_COMMENT_BODIES: list[str] = [
2052 "Agreed — I noticed this too during the last session. @{mention} have you tried adjusting the velocity curve?",
2053 "Good catch. The `{track}` track in `section:{section}` is definitely the culprit here.",
2054 "I think we can fix this with:\n```python\n# Adjust humanization range\nhumanize_ms = 12 # was 5\nvelocity_range = (40, 90) # was (70, 80)\n```",
2055 "This has been bothering me since the first mix. The `beats:{beats}` region needs attention.",
2056 "@{mention} — can you take a look? This is blocking the v1.0 milestone.",
2057 "Fixed in my local branch. The root cause was the `{track}` MIDI channel assignment. Will open a PR.",
2058 "I ran an analysis on the affected region:\n```\nFreq: {freq}Hz Peak: -6dBFS Phase: +12°\n```\nNeeds a notch filter.",
2059 "Confirmed on my system. Happens consistently at bar {bar}. The {track} seems off.",
2060 "Not sure this is the right approach. @{mention} what do you think about using a different technique?",
2061 "This is now tracked in the v1.0 milestone. Should be resolved before release.",
2062 "After further listening, the issue is more subtle than I initially thought. The `section:{section}` transition is the real problem.",
2063 "Tested the fix — sounds much better now. The `track:{track}` now sits properly in the mix.",
2064 "Adding context: this is related to #3 which had the same root cause in `section:{section}`.",
2065 "I think we should prioritize this. The groove feels off and it's the first thing listeners will notice.",
2066 "Will take a pass at this during the next session. @{mention} — can you prepare a reference recording?",
2067 ]
2068
2069 ISSUE_COMMENT_MENTIONS = ["gabriel", "sofia", "marcus", "yuki", "aaliya", "chen", "fatou", "pierre"]
2070 ISSUE_COMMENT_TRACKS = ["bass", "keys", "drums", "strings", "horns", "guitar", "vocals", "pad"]
2071 ISSUE_COMMENT_SECTIONS = ["intro", "verse", "chorus", "bridge", "breakdown", "coda", "outro"]
2072 ISSUE_COMMENT_FREQS = ["80", "200", "400", "800", "2000", "4000", "8000"]
2073
2074
2075 def _make_issue_comment_body(seed: int) -> str:
2076 """Generate a realistic issue comment body with @mention, track refs, and code blocks."""
2077 template = ISSUE_COMMENT_BODIES[seed % len(ISSUE_COMMENT_BODIES)]
2078 mention = ISSUE_COMMENT_MENTIONS[(seed + 1) % len(ISSUE_COMMENT_MENTIONS)]
2079 track = ISSUE_COMMENT_TRACKS[seed % len(ISSUE_COMMENT_TRACKS)]
2080 section = ISSUE_COMMENT_SECTIONS[(seed + 2) % len(ISSUE_COMMENT_SECTIONS)]
2081 bar = (seed % 32) + 1
2082 freq = ISSUE_COMMENT_FREQS[seed % len(ISSUE_COMMENT_FREQS)]
2083 return (template
2084 .replace("{mention}", mention)
2085 .replace("{track}", track)
2086 .replace("{section}", section)
2087 .replace("{bar}", str(bar))
2088 .replace("{beats}", f"{bar}-{bar+4}")
2089 .replace("{freq}", freq))
2090
2091
2092 def _make_issue_musical_refs(body: str) -> list[dict[str, str]]:
2093 """Extract musical context references from a comment body."""
2094 import re
2095 refs: list[dict[str, str]] = []
2096 for m in re.finditer(r"track:(\w+)", body):
2097 refs.append({"type": "track", "value": m.group(1)})
2098 for m in re.finditer(r"section:(\w+)", body):
2099 refs.append({"type": "section", "value": m.group(1)})
2100 for m in re.finditer(r"beats:(\d+-\d+)", body):
2101 refs.append({"type": "beats", "value": m.group(1)})
2102 return refs
2103
2104
2105 # ---------------------------------------------------------------------------
2106 # PR templates
2107 # ---------------------------------------------------------------------------
2108
2109 def _make_prs(repo_id: str, commits: list[dict[str, Any]], owner: str) -> list[dict[str, Any]]:
2110 """Generate 4 template pull requests (open, merged, open, closed) for a repo."""
2111 if len(commits) < 4:
2112 return []
2113 c = commits
2114 return [
2115 dict(pr_id=_uid(f"pr-{repo_id}-1"), repo_id=repo_id,
2116 title="Feat: add counter-melody layer",
2117 body="## Changes\nAdds secondary melodic voice.\n\n## Analysis\nHarmonic tension +0.08.",
2118 state="open", from_branch="feat/counter-melody", to_branch="main",
2119 author=owner,
2120 created_at=_now(days=6)),
2121 dict(pr_id=_uid(f"pr-{repo_id}-2"), repo_id=repo_id,
2122 title="Refactor: humanize all MIDI timing",
2123 body="Applied `muse humanize --natural` to all tracks. Groove score +0.12.",
2124 state="merged", from_branch="fix/humanize-midi", to_branch="main",
2125 merge_commit_id=c[-3]["commit_id"],
2126 author=owner,
2127 created_at=_now(days=14)),
2128 dict(pr_id=_uid(f"pr-{repo_id}-3"), repo_id=repo_id,
2129 title="Experiment: alternate bridge harmony",
2130 body="Trying a tritone substitution approach for the bridge section.",
2131 state="open", from_branch="experiment/bridge-harmony", to_branch="main",
2132 author=owner,
2133 created_at=_now(days=3)),
2134 dict(pr_id=_uid(f"pr-{repo_id}-4"), repo_id=repo_id,
2135 title="Fix: resolve voice-leading errors",
2136 body="Parallel 5ths in bars 7-8 and parallel octaves in bars 15-16 corrected.",
2137 state="closed", from_branch="fix/voice-leading", to_branch="main",
2138 author=owner,
2139 created_at=_now(days=20)),
2140 ]
2141
2142
2143 # ---------------------------------------------------------------------------
2144 # Release templates
2145 # ---------------------------------------------------------------------------
2146
2147 def _make_releases(repo_id: str, commits: list[dict[str, Any]], repo_name: str, owner: str) -> list[dict[str, Any]]:
2148 """Generate 3 releases (v0.1.0 draft, v0.2.0 arrangement, v1.0.0 full) for a repo.
2149
2150 Each release dict includes a deterministic ``release_id`` so downstream
2151 code (release assets seeding) can reference it without a separate DB query.
2152 """
2153 if not commits:
2154 return []
2155 return [
2156 dict(release_id=_uid(f"release-{repo_id}-v0.1.0"),
2157 repo_id=repo_id, tag="v0.1.0", title="Early Draft",
2158 body=f"## v0.1.0 — Early Draft\n\nFirst checkpoint. Basic groove locked in.\n\n### Tracks\n- Main groove\n- Bass foundation\n\n### Technical\nInitial BPM and key established.",
2159 commit_id=commits[min(4, len(commits)-1)]["commit_id"],
2160 download_urls={"midi_bundle": f"/releases/{repo_id}-v0.1.0.zip"},
2161 author=owner,
2162 created_at=_now(days=45)),
2163 dict(release_id=_uid(f"release-{repo_id}-v0.2.0"),
2164 repo_id=repo_id, tag="v0.2.0", title="Arrangement Draft",
2165 body=f"## v0.2.0 — Arrangement Draft\n\nAll major sections sketched.\n\n### What's new\n- Additional instrument layers\n- Section transitions defined\n- Dynamic arc mapped",
2166 commit_id=commits[min(12, len(commits)-1)]["commit_id"],
2167 download_urls={"midi_bundle": f"/releases/{repo_id}-v0.2.0.zip", "mp3": f"/releases/{repo_id}-v0.2.0.mp3"},
2168 author=owner,
2169 created_at=_now(days=25)),
2170 dict(release_id=_uid(f"release-{repo_id}-v1.0.0"),
2171 repo_id=repo_id, tag="v1.0.0", title=f"{repo_name} — Full Release",
2172 body=f"## v1.0.0 — Full Release\n\nProduction-ready state.\n\n### Highlights\n- Complete arrangement with all instruments\n- Mixed and mastered\n- Stems included\n\n### Downloads\nMIDI bundle, MP3 stereo mix, individual stems",
2173 commit_id=commits[-1]["commit_id"],
2174 download_urls={"midi_bundle": f"/releases/{repo_id}-v1.0.0.zip",
2175 "mp3": f"/releases/{repo_id}-v1.0.0.mp3",
2176 "stems": f"/releases/{repo_id}-v1.0.0-stems.zip"},
2177 author=owner,
2178 created_at=_now(days=5)),
2179 ]
2180
2181
2182 # ---------------------------------------------------------------------------
2183 # Session templates
2184 # ---------------------------------------------------------------------------
2185
2186 def _make_sessions(repo_id: str, owner: str, commits: list[dict[str, Any]]) -> list[dict[str, Any]]:
2187 """Generate 6 collaboration sessions per repo; adds a live session for high-traffic repos."""
2188 if len(commits) < 2:
2189 return []
2190 sess = []
2191 collab_map: dict[str, list[tuple[str, ...]]] = {
2192 REPO_NEO_SOUL: [("gabriel", "marcus"), ("gabriel",), ("gabriel", "marcus", "aaliya")],
2193 REPO_MODAL_JAZZ: [("gabriel", "marcus"), ("gabriel",)],
2194 REPO_AMBIENT: [("sofia", "yuki"), ("sofia",), ("sofia", "pierre")],
2195 REPO_AFROBEAT: [("aaliya", "fatou"), ("aaliya",), ("aaliya", "marcus")],
2196 REPO_FUNK_SUITE: [("marcus", "gabriel"), ("marcus",)],
2197 REPO_JAZZ_TRIO: [("marcus",), ("marcus", "gabriel")],
2198 REPO_DRUM_MACHINE: [("fatou",), ("fatou", "aaliya")],
2199 REPO_CHANSON: [("pierre",), ("pierre", "sofia")],
2200 REPO_GRANULAR: [("yuki",), ("yuki", "sofia")],
2201 REPO_MICROTONAL: [("chen",)],
2202 }
2203 collab_groups: list[tuple[str, ...]] = collab_map.get(repo_id) or [(owner,)]
2204 locations = [
2205 "Studio A, São Paulo", "Home studio", "Remote",
2206 "Abbey Road Studio 3", "Electric Lady Studios", "Remote (async)",
2207 "Bedroom studio, Tokyo", "La Fabrique, Marseille",
2208 ]
2209 for i, group in enumerate((collab_groups * 3)[:6]):
2210 start_days = 60 - i * 8
2211 dur_hours = [3, 4, 2, 5, 2, 3][i % 6]
2212 commit_slice = commits[i * 4:i * 4 + 3] if len(commits) > i * 4 + 3 else commits[-2:]
2213 sess.append(dict(
2214 session_id=_uid(f"sess-{repo_id}-{i}"),
2215 repo_id=repo_id,
2216 started_at=_now(days=start_days),
2217 ended_at=_now(days=start_days, hours=-dur_hours),
2218 participants=list(group),
2219 location=locations[i % len(locations)],
2220 intent=f"Session {i+1}: extend arrangement and refine mix",
2221 commits=[c["commit_id"] for c in commit_slice],
2222 notes=f"Productive session. Focused on {'groove' if i % 2 == 0 else 'arrangement'}.",
2223 is_active=False,
2224 created_at=_now(days=start_days),
2225 ))
2226 # Add one live/active session for the first big repo
2227 if repo_id in (REPO_NEO_SOUL, REPO_AFROBEAT):
2228 sess.append(dict(
2229 session_id=_uid(f"sess-{repo_id}-live"),
2230 repo_id=repo_id,
2231 started_at=_now(hours=1),
2232 ended_at=None,
2233 participants=[owner, "marcus"],
2234 location="Studio A — Live",
2235 intent="Live recording session — tracking final takes",
2236 commits=[],
2237 notes="",
2238 is_active=True,
2239 created_at=_now(hours=1),
2240 ))
2241 return sess
2242
2243
2244 # ---------------------------------------------------------------------------
2245 # Webhook + delivery templates
2246 # ---------------------------------------------------------------------------
2247
2248 def _make_webhooks(
2249 repo_id: str, owner: str
2250 ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
2251 """Generate 3 webhook subscriptions and 10–15 deliveries per webhook.
2252
2253 Delivery outcomes cycle through 200 (success), 500 (server error), and
2254 0/timeout patterns so every status is represented in the dataset.
2255 Returns (webhooks, deliveries).
2256 """
2257 webhooks: list[dict[str, Any]] = []
2258 deliveries: list[dict[str, Any]] = []
2259
2260 wh_configs = [
2261 dict(suffix="push", url=f"https://hooks.example.com/{owner}/push",
2262 events=["push"], active=True),
2263 dict(suffix="pr", url=f"https://hooks.example.com/{owner}/pr",
2264 events=["pull_request", "issue"], active=True),
2265 dict(suffix="release", url=f"https://hooks.example.com/{owner}/release",
2266 events=["release"], active=False),
2267 ]
2268
2269 for wh_spec in wh_configs:
2270 wh_suffix: str = wh_spec["suffix"] # type: ignore[assignment]
2271 wh_url: str = wh_spec["url"] # type: ignore[assignment]
2272 wh_events: list[str] = wh_spec["events"] # type: ignore[assignment]
2273 wh_active: bool = wh_spec["active"] # type: ignore[assignment]
2274 wh_id = _uid(f"wh-{repo_id}-{wh_suffix}")
2275 webhooks.append(dict(
2276 webhook_id=wh_id,
2277 repo_id=repo_id,
2278 url=wh_url,
2279 events=wh_events,
2280 secret=_sha(f"secret-{repo_id}-{wh_suffix}")[:32],
2281 active=wh_active,
2282 created_at=_now(days=60),
2283 ))
2284
2285 # 10–15 deliveries per webhook; cycle through status patterns.
2286 n_deliveries = 10 + (int(_sha(f"nd-{repo_id}-{wh_suffix}")[:2], 16) % 6)
2287 for j in range(n_deliveries):
2288 # Pattern (period 7): 200, 200, 500, 200, 200, timeout, 200
2289 pattern = j % 7
2290 if pattern in (0, 1, 3, 4, 6):
2291 success, status, resp_body = True, 200, '{"ok": true}'
2292 elif pattern == 2:
2293 success, status, resp_body = False, 500, '{"error": "Internal Server Error"}'
2294 else: # pattern == 5 → timeout
2295 success, status, resp_body = False, 0, ""
2296
2297 event_type = wh_events[j % len(wh_events)]
2298 payload_data = (
2299 f'{{"event": "{event_type}", "repo": "{repo_id}", '
2300 f'"attempt": {(j % 3) + 1}, "ts": "{_now(days=j).isoformat()}"}}'
2301 )
2302 deliveries.append(dict(
2303 delivery_id=_uid(f"del-{wh_id}-{j}"),
2304 webhook_id=wh_id,
2305 event_type=event_type,
2306 payload=payload_data,
2307 attempt=(j % 3) + 1,
2308 success=success,
2309 response_status=status,
2310 response_body=resp_body,
2311 delivered_at=_now(days=j),
2312 ))
2313
2314 return webhooks, deliveries
2315
2316
2317 # ---------------------------------------------------------------------------
2318 # PR comment templates
2319 # ---------------------------------------------------------------------------
2320
2321 _PR_COMMENT_POOL: list[dict[str, Any]] = [
2322 dict(target_type="general", target_track=None, target_beat_start=None,
2323 target_beat_end=None, target_note_pitch=None,
2324 body="Overall approach looks good. The counter-melody adds the harmonic tension the arrangement was missing. LGTM with minor comments below."),
2325 dict(target_type="track", target_track="bass", target_beat_start=None,
2326 target_beat_end=None, target_note_pitch=None,
2327 body="The bass track humanization is much improved. Ghost notes at beats 2.5 and 3.5 are well-placed.\n\n> Suggest reducing velocity on the ghost at beat 3.5 by 10 units."),
2328 dict(target_type="region", target_track="keys", target_beat_start=9.0,
2329 target_beat_end=17.0, target_note_pitch=None,
2330 body="Bars 9-16: the Rhodes voicing here feels crowded. Try removing the 5th — root + 3rd + 7th is cleaner."),
2331 dict(target_type="note", target_track="trumpet", target_beat_start=24.0,
2332 target_beat_end=None, target_note_pitch=84,
2333 body="This high C (MIDI 84) at beat 24 is above idiomatic range. Transpose down an octave to C5 (MIDI 72)."),
2334 dict(target_type="general", target_track=None, target_beat_start=None,
2335 target_beat_end=None, target_note_pitch=None,
2336 body="The tritone substitution is elegant. I'd approve as-is but @gabriel should confirm alignment with the arrangement plan."),
2337 dict(target_type="track", target_track="drums", target_beat_start=None,
2338 target_beat_end=None, target_note_pitch=None,
2339 body="Drum humanization is a big improvement. Hi-hat timing now feels natural.\n\nOne nit: the ride bell at beat 4 is slightly too loud — try velocity 85 instead of 100."),
2340 dict(target_type="region", target_track="strings", target_beat_start=25.0,
2341 target_beat_end=33.0, target_note_pitch=None,
2342 body="Bars 25-32: pizzicato countermelody is beautifully voiced. Staggered entries work well. No changes needed."),
2343 dict(target_type="note", target_track="bass", target_beat_start=13.0,
2344 target_beat_end=None, target_note_pitch=42,
2345 body="This F# (MIDI 42) at beat 13 creates a very dark sub. If intentional — great. If not, try B1 (MIDI 47)."),
2346 dict(target_type="general", target_track=None, target_beat_start=None,
2347 target_beat_end=None, target_note_pitch=None,
2348 body="Reviewed all four parallel-5th instances. Bars 7-8 and 15-16 are fixed. Bars 23-24 still have a parallel octave between violin and cello — please fix before merging."),
2349 dict(target_type="track", target_track="vocals", target_beat_start=None,
2350 target_beat_end=None, target_note_pitch=None,
2351 body="Vocal sibilance is still present on sustained S sounds. De-esser threshold needs to be 3dB lower. Otherwise the PR is ready."),
2352 ]
2353
2354
2355 def _make_pr_comments(
2356 pr_id: str, repo_id: str, pr_n: int, owner: str, days_ago: int
2357 ) -> list[dict[str, Any]]:
2358 """Generate 3–8 inline review comments for a single pull request.
2359
2360 Uses the rotating _PR_COMMENT_POOL with author cycling across the user roster.
2361 Some comments thread as replies via parent_comment_id.
2362 """
2363 pool_size = len(_PR_COMMENT_POOL)
2364 authors = ["gabriel", "sofia", "marcus", "yuki", "aaliya", "chen", "fatou", "pierre"]
2365 n_comments = 3 + (pr_n % 6) # yields 3–8 per PR
2366 comments: list[dict[str, Any]] = []
2367 first_comment_id: str | None = None
2368
2369 for i in range(n_comments):
2370 pool_entry = _PR_COMMENT_POOL[(pr_n * 5 + i * 3) % pool_size]
2371 author = authors[(pr_n + i + 2) % len(authors)]
2372 if author == owner and i == 0:
2373 author = authors[(pr_n + i + 3) % len(authors)]
2374
2375 comment_id = _uid(f"pr-cmt-{repo_id}-{pr_n}-{i}")
2376 parent_id: str | None = None
2377 # Comments 3+ thread under the first comment to simulate replies.
2378 if i >= 3 and first_comment_id is not None:
2379 parent_id = first_comment_id
2380
2381 comments.append(dict(
2382 comment_id=comment_id,
2383 pr_id=pr_id,
2384 repo_id=repo_id,
2385 author=author,
2386 body=pool_entry["body"],
2387 target_type=pool_entry["target_type"],
2388 target_track=pool_entry["target_track"],
2389 target_beat_start=pool_entry["target_beat_start"],
2390 target_beat_end=pool_entry["target_beat_end"],
2391 target_note_pitch=pool_entry["target_note_pitch"],
2392 parent_comment_id=parent_id,
2393 created_at=_now(days=days_ago - i),
2394 ))
2395 if i == 0:
2396 first_comment_id = comment_id
2397
2398 return comments
2399
2400
2401 # ---------------------------------------------------------------------------
2402 # Main seed function
2403 # ---------------------------------------------------------------------------
2404
2405 async def seed(db: AsyncSession, force: bool = False) -> None:
2406 """Populate all MuseHub tables with a realistic stress-test dataset.
2407
2408 Inserts users, repos, commits, branches, issues, PRs, releases, sessions,
2409 social graph (stars, follows, watches, comments, reactions, notifications,
2410 forks, view/download events), and the full Muse VCS layer (objects,
2411 snapshots, commits, tags). Pass force=True to wipe and re-seed existing data.
2412 """
2413 print("🌱 Seeding MuseHub stress-test dataset…")
2414
2415 result = await db.execute(text("SELECT COUNT(*) FROM musehub_repos"))
2416 existing = result.scalar() or 0
2417
2418 if existing > 0 and not force:
2419 print(f" ⚠️ {existing} repo(s) already exist — skipping. Pass --force to wipe and reseed.")
2420 _print_urls()
2421 return
2422
2423 if existing > 0 and force:
2424 print(" 🗑 --force: clearing existing seed data…")
2425 for tbl in [
2426 "muse_access_tokens",
2427 # Muse VCS — innermost first (tags depend on commits, commits depend on snapshots)
2428 "muse_tags", "muse_commits", "muse_snapshots", "muse_objects",
2429 # MuseHub — children before parents (FK order)
2430 "musehub_download_events", "musehub_view_events", "musehub_forks",
2431 "musehub_notifications", "musehub_watches", "musehub_follows",
2432 "musehub_reactions", "musehub_comments",
2433 "musehub_render_jobs",
2434 "musehub_events",
2435 # Stash children before stash (FK to muse_users + repos)
2436 "musehub_stash_entries", "musehub_stash",
2437 # Collaborators (FK to muse_users + repos)
2438 "musehub_collaborators",
2439 "musehub_stars", "musehub_sessions",
2440 # Release assets before releases
2441 "musehub_release_assets", "musehub_releases",
2442 "musehub_webhook_deliveries", "musehub_webhooks",
2443 # PR children before pull_requests
2444 "musehub_pr_labels", "musehub_pr_comments", "musehub_pr_reviews",
2445 "musehub_pull_requests",
2446 # Issue children before issues; milestones after (SET NULL FK)
2447 "musehub_issue_labels", "musehub_issue_milestones",
2448 "musehub_issue_comments",
2449 "musehub_issues", "musehub_milestones",
2450 # Labels after issues/PRs
2451 "musehub_labels",
2452 "musehub_branches",
2453 "musehub_objects", "musehub_commits", "musehub_repos",
2454 "musehub_profiles",
2455 # muse_users last (other tables FK to it)
2456 "muse_users",
2457 ]:
2458 await db.execute(text(f"DELETE FROM {tbl}"))
2459 await db.flush()
2460
2461 # ── 1. muse_users (required FK for collaborators + stash) ─────────────
2462 # Mirrors the same user IDs used in musehub_profiles so the FK chain is
2463 # consistent across the whole schema.
2464 all_user_ids_and_names = list(USERS) + list(COMPOSER_USERS)
2465 for uid, _uname, _bio in all_user_ids_and_names:
2466 db.add(User(
2467 id=uid,
2468 created_at=_now(days=120),
2469 updated_at=_now(days=1),
2470 ))
2471 print(f" ✅ muse_users: {len(all_user_ids_and_names)}")
2472
2473 await db.flush()
2474
2475 # ── 1b. User profiles (musehub_profiles) ──────────────────────
2476 # Pinned repos show the owner's most prominent repos on their profile page.
2477 _PROFILE_PINS: dict[str, list[str]] = {
2478 GABRIEL: [REPO_NEO_SOUL, REPO_MODAL_JAZZ, REPO_NEO_BAROQUE, REPO_COMMUNITY, REPO_WELL_TEMPERED_RAG],
2479 SOFIA: [REPO_AMBIENT, REPO_NOCTURNE_VARIATIONS],
2480 MARCUS: [REPO_FUNK_SUITE, REPO_JAZZ_TRIO, REPO_RAGTIME_EDM, REPO_MOONLIGHT_JAZZ],
2481 YUKI: [REPO_GRANULAR, REPO_CLAIR_DECONSTRUCTED],
2482 AALIYA: [REPO_AFROBEAT, REPO_JAZZ_CHOPIN, REPO_RAGTIME_AFROBEAT],
2483 CHEN: [REPO_MICROTONAL, REPO_FILM_SCORE],
2484 FATOU: [REPO_DRUM_MACHINE, REPO_POLYRHYTHM],
2485 PIERRE: [REPO_CHANSON],
2486 BACH: [REPO_WTC, REPO_GOLDBERG],
2487 CHOPIN: [REPO_NOCTURNES],
2488 SCOTT_JOPLIN: [REPO_MAPLE_LEAF],
2489 KEVIN_MACLEOD: [REPO_CIN_STRINGS],
2490 KAI_ENGEL: [REPO_KAI_AMBIENT],
2491 BEETHOVEN: [REPO_MOONLIGHT, REPO_BEETHOVEN_SONATAS],
2492 MOZART: [REPO_MOZART_K331],
2493 DEBUSSY: [REPO_SUITE_BERGAMASQUE],
2494 SATIE: [REPO_GYMNOPEDIE],
2495 SCHUBERT: [REPO_IMPROMPTUS],
2496 }
2497 all_user_profiles = list(USERS) + list(COMPOSER_USERS)
2498 for uid, uname, _bio in all_user_profiles:
2499 p = PROFILE_DATA.get(uid, {})
2500 db.add(MusehubProfile(
2501 user_id=uid,
2502 username=uname,
2503 display_name=str(p["display_name"]) if p.get("display_name") else uname,
2504 bio=str(p["bio"]) if p.get("bio") else _bio,
2505 avatar_url=f"https://api.dicebear.com/7.x/avataaars/svg?seed={uname}",
2506 location=str(p["location"]) if p.get("location") else None,
2507 website_url=str(p["website_url"]) if p.get("website_url") else None,
2508 twitter_handle=str(p["twitter_handle"]) if p.get("twitter_handle") else None,
2509 is_verified=bool(p.get("is_verified", False)),
2510 cc_license=str(p["cc_license"]) if p.get("cc_license") else None,
2511 pinned_repo_ids=_PROFILE_PINS.get(uid, []),
2512 ))
2513 verified_count = sum(1 for uid, _, __ in all_user_profiles if PROFILE_DATA.get(uid, {}).get("is_verified"))
2514 pd_count = sum(1 for uid, _, __ in COMPOSER_USERS if PROFILE_DATA.get(uid, {}).get("cc_license") == "Public Domain")
2515 cc_count = sum(1 for uid, _, __ in COMPOSER_USERS if PROFILE_DATA.get(uid, {}).get("cc_license") == "CC BY 4.0")
2516 print(f" ✅ Profiles: {len(all_user_profiles)} ({len(USERS)} community + {len(COMPOSER_USERS)} composers: {pd_count} public domain, {cc_count} CC BY 4.0)")
2517
2518 # ── 2. Repos ──────────────────────────────────────────────────
2519 # Build owner → cc_license lookup from PROFILE_DATA so repos inherit their
2520 # owner's Creative Commons licence for the sidebar filter.
2521 # Normalize to match UI filter option values (CC0, CC BY, etc.).
2522 def _normalize_license(raw: str) -> str:
2523 if not raw:
2524 return ""
2525 if "public domain" in raw.lower():
2526 return "CC0"
2527 if raw.upper().startswith("CC BY-SA"):
2528 return "CC BY-SA"
2529 if raw.upper().startswith("CC BY-NC"):
2530 return "CC BY-NC"
2531 if raw.upper().startswith("CC BY"):
2532 return "CC BY"
2533 if raw.upper().startswith("CC0"):
2534 return "CC0"
2535 return raw
2536
2537 owner_license_map: dict[str, str] = {
2538 uid: _normalize_license(pd.get("cc_license") or "")
2539 for uid, pd in PROFILE_DATA.items()
2540 }
2541 # Also map by username → license for convenience.
2542 username_license_map: dict[str, str] = {}
2543 for uid, uname, _ in COMPOSER_USERS:
2544 lic = owner_license_map.get(uid, "")
2545 if lic:
2546 username_license_map[uname] = lic
2547
2548 all_repos = list(REPOS) + list(GENRE_REPOS)
2549 for r in all_repos:
2550 repo_license = r.get("license") or username_license_map.get(r["owner"], "")
2551 db.add(MusehubRepo(
2552 repo_id=r["repo_id"],
2553 name=r["name"],
2554 owner=r["owner"],
2555 slug=r["slug"],
2556 owner_user_id=r["owner_user_id"],
2557 visibility=r["visibility"],
2558 description=r["description"],
2559 tags=r["tags"],
2560 key_signature=r["key_signature"],
2561 tempo_bpm=r["tempo_bpm"],
2562 created_at=_now(days=r["days_ago"]),
2563 settings={"license": repo_license} if repo_license else None,
2564 ))
2565 print(f" ✅ Repos: {len(all_repos)} ({len(REPOS)} original community + {len(GENRE_REPOS)} genre/archive/remix)")
2566
2567 await db.flush()
2568
2569 # ── 3. Commits + Branches ─────────────────────────────────────
2570 all_commits: dict[str, list[dict[str, Any]]] = {}
2571 total_commits = 0
2572 for r in all_repos:
2573 repo_id = r["repo_id"]
2574 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2575 n = COMMIT_COUNTS.get(repo_id, 20)
2576 commits = _make_commits(repo_id, rkey, n)
2577 all_commits[repo_id] = commits
2578 total_commits += len(commits)
2579 for c in commits:
2580 db.add(MusehubCommit(**c))
2581 # main branch always points to HEAD
2582 db.add(MusehubBranch(repo_id=repo_id, name="main",
2583 head_commit_id=commits[-1]["commit_id"]))
2584 if repo_id in GENRE_REPO_BRANCHES:
2585 # Genre archive repos: use specific named branches
2586 for branch_name, offset in GENRE_REPO_BRANCHES[repo_id]:
2587 idx = max(0, len(commits) - 1 - offset)
2588 db.add(MusehubBranch(
2589 repo_id=repo_id,
2590 name=branch_name,
2591 head_commit_id=commits[idx]["commit_id"],
2592 ))
2593 else:
2594 # Original repos: generic feature branches
2595 if len(commits) > 10:
2596 db.add(MusehubBranch(repo_id=repo_id, name="feat/develop",
2597 head_commit_id=commits[-4]["commit_id"]))
2598 if len(commits) > 20:
2599 db.add(MusehubBranch(repo_id=repo_id, name="experiment/alternate-harmony",
2600 head_commit_id=commits[-8]["commit_id"]))
2601 print(f" ✅ Commits: {total_commits} across {len(all_repos)} repos")
2602
2603 await db.flush()
2604
2605 # ── 4. Objects (track breakdown bar) ──────────────────────────
2606 obj_count = 0
2607 for r in all_repos:
2608 repo_id = r["repo_id"]
2609 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2610 tracks = REPO_TRACKS.get(rkey, REPO_TRACKS["neo-soul"])
2611 commits = all_commits.get(repo_id, [])
2612 if not commits:
2613 continue
2614 # Attach objects to the last 3 commits
2615 for commit in commits[-3:]:
2616 cid = commit["commit_id"]
2617 for role, path in tracks:
2618 obj_id = f"sha256:{_sha(f'{cid}-{path}')}"
2619 db.add(MusehubObject(
2620 object_id=obj_id,
2621 repo_id=repo_id,
2622 path=path,
2623 size_bytes=len(path) * 1024,
2624 disk_path=f"/app/objects/{repo_id}/{obj_id[7:15]}.mid",
2625 created_at=commit["timestamp"],
2626 ))
2627 obj_count += 1
2628 print(f" ✅ Objects: {obj_count} track files")
2629
2630 await db.flush()
2631
2632 # ── 4b. Labels (scoped to each repo — seeded before issues/PRs) ───────────
2633 _LABEL_DEFS: list[tuple[str, str, str]] = [
2634 # (name, color, description)
2635 ("bug", "#d73a4a", "Something isn't working correctly"),
2636 ("enhancement", "#a2eeef", "New feature or improvement request"),
2637 ("documentation","#0075ca", "Documentation update or correction"),
2638 ("question", "#d876e3", "Further information requested"),
2639 ("wontfix", "#ffffff", "This will not be addressed"),
2640 ("good first issue", "#7057ff", "Good for newcomers to the project"),
2641 ("help wanted", "#008672", "Extra attention needed"),
2642 ("in progress", "#e4e669", "Currently being worked on"),
2643 ("blocked", "#e11d48", "Blocked by another issue or dependency"),
2644 ("harmony", "#fbbf24", "Harmonic or tonal issue"),
2645 ("timing", "#6366f1", "Timing, groove or quantization issue"),
2646 ("mixing", "#10b981", "Mix balance, levels or EQ"),
2647 ("arrangement", "#f97316", "Arrangement or structure feedback"),
2648 ]
2649 # Structure: repo_id → {label_name → label_id}
2650 label_id_map: dict[str, dict[str, str]] = {}
2651 label_count = 0
2652 for r in all_repos:
2653 repo_id = r["repo_id"]
2654 label_id_map[repo_id] = {}
2655 for lname, lcolor, ldesc in _LABEL_DEFS:
2656 lid = _uid(f"label-{repo_id}-{lname}")
2657 db.add(MusehubLabel(
2658 id=lid,
2659 repo_id=repo_id,
2660 name=lname,
2661 color=lcolor,
2662 description=ldesc,
2663 created_at=_now(days=r["days_ago"]),
2664 ))
2665 label_id_map[repo_id][lname] = lid
2666 label_count += 1
2667 print(f" ✅ Labels: {label_count} ({len(_LABEL_DEFS)} per repo × {len(all_repos)} repos)")
2668
2669 await db.flush()
2670
2671 # ── 5a. Milestones (seed before issues so milestone_id can be referenced) ──
2672 # Structure: repo_id → {milestone_n → milestone_id}
2673 milestone_id_map: dict[str, dict[int, str]] = {}
2674 milestone_count = 0
2675 for r in REPOS:
2676 repo_id = r["repo_id"]
2677 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2678 ms_list = MILESTONE_TEMPLATES.get(rkey, [])
2679 if not ms_list:
2680 continue
2681 milestone_id_map[repo_id] = {}
2682 for ms in ms_list:
2683 mid = _uid(f"milestone-{repo_id}-{ms['n']}")
2684 due = _now(days=-ms["due_days"]) if ms.get("due_days") else None
2685 db.add(MusehubMilestone(
2686 milestone_id=mid,
2687 repo_id=repo_id,
2688 number=ms["n"],
2689 title=ms["title"],
2690 description=ms["description"],
2691 state=ms["state"],
2692 author=r["owner"],
2693 due_on=due,
2694 created_at=_now(days=r["days_ago"]),
2695 ))
2696 milestone_id_map[repo_id][ms["n"]] = mid
2697 milestone_count += 1
2698 print(f" ✅ Milestones: {milestone_count}")
2699
2700 await db.flush()
2701
2702 # ── 5b. Issues (track IDs for comment and milestone-link seeding) ──────────
2703 issue_count = 0
2704 # Structure: repo_id → {issue_n → issue_id}
2705 issue_id_map: dict[str, dict[int, str]] = {}
2706 for r in all_repos:
2707 repo_id = r["repo_id"]
2708 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2709 issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES)
2710 days_base = r["days_ago"]
2711 # Determine milestone assignment map for this repo: issue_n → milestone_n
2712 ms_assignments = MILESTONE_ISSUE_ASSIGNMENTS.get(rkey, {})
2713 issue_to_ms: dict[int, int] = {}
2714 for ms_number, issue_ns in ms_assignments.items():
2715 for iss_n in issue_ns:
2716 issue_to_ms[iss_n] = ms_number
2717
2718 issue_id_map[repo_id] = {}
2719 for iss in issue_list:
2720 iid = _uid(f"issue-{repo_id}-{iss['n']}")
2721 assigned_ms_n: int | None = issue_to_ms.get(iss["n"])
2722 ms_id: str | None = milestone_id_map.get(repo_id, {}).get(assigned_ms_n) if assigned_ms_n else None
2723 db.add(MusehubIssue(
2724 issue_id=iid,
2725 repo_id=repo_id,
2726 number=iss["n"],
2727 state=iss["state"],
2728 title=iss["title"],
2729 body=iss["body"],
2730 labels=iss["labels"],
2731 author=r["owner"],
2732 milestone_id=ms_id,
2733 created_at=_now(days=days_base - iss["n"] * 2),
2734 ))
2735 issue_id_map[repo_id][iss["n"]] = iid
2736 # Also populate MusehubIssueMilestone join table for repos with milestones
2737 if ms_id:
2738 db.add(MusehubIssueMilestone(issue_id=iid, milestone_id=ms_id))
2739 issue_count += 1
2740 print(f" ✅ Issues: {issue_count}")
2741
2742 await db.flush()
2743
2744 # ── 5b-ii. Issue labels (many-to-many join) ────────────────────────────────
2745 _ISSUE_LABEL_PICKS: list[list[str]] = [
2746 # Cycling pattern of label combos assigned to issues by index
2747 ["bug"],
2748 ["enhancement"],
2749 ["bug", "in progress"],
2750 ["question"],
2751 ["harmony"],
2752 ["timing"],
2753 ["mixing"],
2754 ["arrangement"],
2755 ["enhancement", "help wanted"],
2756 ["bug", "blocked"],
2757 ["documentation"],
2758 ["good first issue"],
2759 ["wontfix"],
2760 ["enhancement", "in progress"],
2761 ["harmony", "help wanted"],
2762 ]
2763 issue_label_count = 0
2764 for r in all_repos:
2765 repo_id = r["repo_id"]
2766 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2767 issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES)
2768 repo_labels = label_id_map.get(repo_id, {})
2769 for iss in issue_list:
2770 il_iid: str | None = issue_id_map.get(repo_id, {}).get(iss["n"])
2771 if not il_iid:
2772 continue
2773 picks = _ISSUE_LABEL_PICKS[iss["n"] % len(_ISSUE_LABEL_PICKS)]
2774 for lname in picks:
2775 il_lid: str | None = repo_labels.get(lname)
2776 if il_lid:
2777 db.add(MusehubIssueLabel(issue_id=il_iid, label_id=il_lid))
2778 issue_label_count += 1
2779 print(f" ✅ Issue labels: {issue_label_count}")
2780
2781 await db.flush()
2782
2783 # ── 5c. Issue comments (5-10 per issue, with @mentions and code blocks) ────
2784 issue_comment_count = 0
2785 users_list = [u[1] for u in USERS]
2786 for r in REPOS[:10]: # Comments on all non-fork repos
2787 repo_id = r["repo_id"]
2788 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2789 issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES)
2790 for iss in issue_list:
2791 iss_iid = issue_id_map.get(repo_id, {}).get(iss["n"])
2792 if not iss_iid:
2793 continue
2794 # 5-10 comments per issue (varies by issue number parity)
2795 n_comments = 5 + (iss["n"] % 6)
2796 iss_cmt_parent: str | None = None
2797 for j in range(n_comments):
2798 cmt_seed = hash(repo_id + str(iss["n"]) + str(j)) % 1000
2799 body = _make_issue_comment_body(cmt_seed)
2800 musical_refs = _make_issue_musical_refs(body)
2801 author_idx = (abs(hash(repo_id)) + iss["n"] + j) % len(users_list)
2802 author = users_list[author_idx]
2803 cid = _uid(f"iss-comment-{repo_id}-{iss['n']}-{j}")
2804 db.add(MusehubIssueComment(
2805 comment_id=cid,
2806 issue_id=iss_iid,
2807 repo_id=repo_id,
2808 author=author,
2809 body=body,
2810 parent_id=iss_cmt_parent if j > 0 and j % 3 == 0 else None,
2811 musical_refs=musical_refs,
2812 created_at=_now(days=r["days_ago"] - iss["n"] * 2, hours=j * 2),
2813 ))
2814 issue_comment_count += 1
2815 # First comment becomes parent for threaded replies
2816 if j == 0:
2817 iss_cmt_parent = cid
2818 print(f" ✅ Issue comments: {issue_comment_count}")
2819
2820 await db.flush()
2821
2822 # ── 6. Pull Requests ──────────────────────────────────────────
2823 pr_count = 0
2824 pr_ids: dict[str, list[str]] = {}
2825 for r in all_repos:
2826 repo_id = r["repo_id"]
2827 commits = all_commits.get(repo_id, [])
2828 prs = _make_prs(repo_id, commits, r["owner"])
2829 pr_ids[repo_id] = [p["pr_id"] for p in prs]
2830 for pr in prs:
2831 db.add(MusehubPullRequest(**pr))
2832 pr_count += 1
2833 print(f" ✅ Pull Requests: {pr_count}")
2834
2835 await db.flush()
2836
2837 # ── 6b. PR comments (3-8 per PR with target_type variety) ─────────────────
2838 PR_COMMENT_BODIES: list[tuple[str, str, str | None, float | None, float | None, int | None]] = [
2839 # (body, target_type, target_track, beat_start, beat_end, note_pitch)
2840 ("General: this PR looks good overall. The groove change is an improvement.",
2841 "general", None, None, None, None),
2842 ("The `bass` track changes in this PR need review — the wah envelope is still too slow.",
2843 "track", "bass", None, None, None),
2844 ("This region (beats 16-24) sounds much better with the humanized timing.",
2845 "region", "keys", 16.0, 24.0, None),
2846 ("The C#4 (MIDI 61) note in the Rhodes feels misplaced — should be D4 for the chord.",
2847 "note", "keys", None, None, 61),
2848 ("Great improvement in the horns section. The harmony is now correct.",
2849 "track", "horns", None, None, None),
2850 ("The beat 1-8 region on the bass now locks properly with the kick.",
2851 "region", "bass", 1.0, 8.0, None),
2852 ("The G3 (MIDI 55) in bar 7 creates an unwanted clash. Remove or lower octave.",
2853 "note", "strings", None, None, 55),
2854 ("Overall this PR solves the main issue. LGTM with minor nits.",
2855 "general", None, None, None, None),
2856 ("The `drums` ghost notes are much improved — much more human now.",
2857 "track", "drums", None, None, None),
2858 ("Beats 32-40 on the guitar feel slightly rushed. Did you check the quantize grid?",
2859 "region", "guitar", 32.0, 40.0, None),
2860 ]
2861
2862 pr_comment_count = 0
2863 for r in REPOS[:10]:
2864 repo_id = r["repo_id"]
2865 for pr_id_str in pr_ids.get(repo_id, []):
2866 # 3-8 comments per PR
2867 n_pr_comments = 3 + (abs(hash(pr_id_str)) % 6)
2868 parent_pr_cid: str | None = None
2869 for k in range(n_pr_comments):
2870 tmpl_idx = (abs(hash(pr_id_str)) + k) % len(PR_COMMENT_BODIES)
2871 body, ttype, ttrack, tbs, tbe, tnp = PR_COMMENT_BODIES[tmpl_idx]
2872 author_idx = (abs(hash(repo_id)) + k + 1) % len(users_list)
2873 pr_cid = _uid(f"pr-comment-{pr_id_str}-{k}")
2874 db.add(MusehubPRComment(
2875 comment_id=pr_cid,
2876 pr_id=pr_id_str,
2877 repo_id=repo_id,
2878 author=users_list[author_idx],
2879 body=body,
2880 target_type=ttype,
2881 target_track=ttrack,
2882 target_beat_start=tbs,
2883 target_beat_end=tbe,
2884 target_note_pitch=tnp,
2885 parent_comment_id=parent_pr_cid if k > 0 and k % 4 == 0 else None,
2886 created_at=_now(days=7 - k),
2887 ))
2888 pr_comment_count += 1
2889 if k == 0:
2890 parent_pr_cid = pr_cid
2891 print(f" ✅ PR comments: {pr_comment_count}")
2892
2893 await db.flush()
2894
2895 # ── 6c. PR Reviews (reviewer assignment + approved/changes_requested) ──────
2896 _PR_REVIEW_STATES = ["approved", "approved", "changes_requested", "pending", "dismissed"]
2897 _PR_REVIEW_BODIES = [
2898 "LGTM — the harmonic changes are solid and the voice-leading is now clean.",
2899 "Approved. The groove is much tighter after the timing adjustments.",
2900 "Changes requested: the bass still feels muddy in bars 9-16. Please reduce low-mids.",
2901 "Pending review — I'll listen through the changes this weekend.",
2902 "Approved with nits: the coda could be shorter, but the core change is correct.",
2903 "Changes requested: parallel fifths still present in bar 7 on the strings voice.",
2904 "LGTM — the new arrangement section is exactly what the composition needed.",
2905 ]
2906 pr_review_count = 0
2907 for r in REPOS[:10]:
2908 repo_id = r["repo_id"]
2909 owner = r["owner"]
2910 for pr_id_str in pr_ids.get(repo_id, []):
2911 # 1-2 reviewers per PR, drawn from the community pool (not the PR author)
2912 n_reviewers = 1 + (abs(hash(pr_id_str)) % 2)
2913 reviewers_pool = [u for u in users_list if u != owner]
2914 for ri in range(n_reviewers):
2915 reviewer = reviewers_pool[(abs(hash(pr_id_str)) + ri) % len(reviewers_pool)]
2916 state = _PR_REVIEW_STATES[(abs(hash(pr_id_str)) + ri) % len(_PR_REVIEW_STATES)]
2917 body = _PR_REVIEW_BODIES[(abs(hash(pr_id_str)) + ri) % len(_PR_REVIEW_BODIES)]
2918 submitted = _now(days=5 - ri) if state != "pending" else None
2919 db.add(MusehubPRReview(
2920 id=_uid(f"pr-review-{pr_id_str}-{ri}"),
2921 pr_id=pr_id_str,
2922 reviewer_username=reviewer,
2923 state=state,
2924 body=body if state != "pending" else None,
2925 submitted_at=submitted,
2926 created_at=_now(days=7 - ri),
2927 ))
2928 pr_review_count += 1
2929 print(f" ✅ PR reviews: {pr_review_count}")
2930
2931 await db.flush()
2932
2933 # ── 6d. PR Labels (label tags on pull requests) ────────────────────────────
2934 _PR_LABEL_PICKS: list[list[str]] = [
2935 ["enhancement"],
2936 ["bug"],
2937 ["enhancement", "in progress"],
2938 ["wontfix"],
2939 ]
2940 pr_label_count = 0
2941 for r in all_repos:
2942 repo_id = r["repo_id"]
2943 repo_labels = label_id_map.get(repo_id, {})
2944 for pi, pr_id_str in enumerate(pr_ids.get(repo_id, [])):
2945 picks = _PR_LABEL_PICKS[pi % len(_PR_LABEL_PICKS)]
2946 for lname in picks:
2947 prl_lid: str | None = repo_labels.get(lname)
2948 if prl_lid:
2949 db.add(MusehubPRLabel(pr_id=pr_id_str, label_id=prl_lid))
2950 pr_label_count += 1
2951 print(f" ✅ PR labels: {pr_label_count}")
2952
2953 await db.flush()
2954
2955 # ── 7. Releases ───────────────────────────────────────────────
2956 release_count = 0
2957 release_tags: dict[str, list[str]] = {}
2958 for r in all_repos:
2959 repo_id = r["repo_id"]
2960 commits = all_commits.get(repo_id, [])
2961 releases = _make_releases(repo_id, commits, r["name"], r["owner"])
2962 release_tags[repo_id] = [rel["tag"] for rel in releases]
2963 for rel in releases:
2964 db.add(MusehubRelease(**rel))
2965 release_count += 1
2966 print(f" ✅ Releases: {release_count}")
2967
2968 await db.flush()
2969
2970 # ── 7b. Release Assets (downloadable file attachments per release) ─────────
2971 _ASSET_TYPES: list[tuple[str, str, str, int]] = [
2972 # (name_suffix, label, content_type, approx_size_bytes)
2973 ("-midi-bundle.zip", "MIDI Bundle", "application/zip", 2_400_000),
2974 ("-stereo-mix.mp3", "Stereo Mix", "audio/mpeg", 8_200_000),
2975 ("-stems.zip", "Stems Archive", "application/zip", 42_000_000),
2976 ("-score.pdf", "Score PDF", "application/pdf", 1_100_000),
2977 ("-metadata.json", "Metadata", "application/json", 18_000),
2978 ]
2979 # Only full releases (v1.0.0) get all 5 assets; earlier releases get 2.
2980 release_asset_count = 0
2981 for r in all_repos:
2982 repo_id = r["repo_id"]
2983 tags = release_tags.get(repo_id, [])
2984 for ti, tag in enumerate(tags):
2985 # release_id is deterministic — matches what _make_releases sets
2986 rel_id = _uid(f"release-{repo_id}-{tag}")
2987 # Full release gets all asset types; earlier releases get 2
2988 n_assets = len(_ASSET_TYPES) if ti == len(tags) - 1 else 2
2989 base_slug = r["slug"]
2990 for ai, (sfx, label, ctype, base_size) in enumerate(_ASSET_TYPES[:n_assets]):
2991 dl_count = max(0, (50 - ti * 15) - ai * 5 + abs(hash(repo_id + tag)) % 20)
2992 db.add(MusehubReleaseAsset(
2993 asset_id=_uid(f"asset-{rel_id}-{ai}"),
2994 release_id=rel_id,
2995 repo_id=repo_id,
2996 name=f"{base_slug}-{tag}{sfx}",
2997 label=label,
2998 content_type=ctype,
2999 size=base_size + abs(hash(repo_id)) % 500_000,
3000 download_url=f"/releases/{repo_id}/{tag}{sfx}",
3001 download_count=dl_count,
3002 created_at=_now(days=max(1, 45 - ti * 20)),
3003 ))
3004 release_asset_count += 1
3005 print(f" ✅ Release assets: {release_asset_count}")
3006
3007 await db.flush()
3008
3009 # ── 8. Sessions ───────────────────────────────────────────────
3010 session_count = 0
3011 session_ids: dict[str, list[str]] = {}
3012 for r in all_repos:
3013 repo_id = r["repo_id"]
3014 commits = all_commits.get(repo_id, [])
3015 sessions = _make_sessions(repo_id, r["owner"], commits)
3016 session_ids[repo_id] = [s["session_id"] for s in sessions]
3017 for sess in sessions:
3018 db.add(MusehubSession(**sess))
3019 session_count += 1
3020 print(f" ✅ Sessions: {session_count}")
3021
3022 await db.flush()
3023
3024 # ── 8b. Collaborators (repo access beyond owner) ───────────────────────────
3025 # Each active repo gets 1-3 collaborators from the community pool.
3026 # Collaborators have write permission; the most active repos get an admin.
3027 _COLLAB_CONFIGS: list[tuple[str, str, list[tuple[str, str]]]] = [
3028 # (repo_id, owner_user_id, [(collab_user_id, permission)])
3029 (REPO_NEO_SOUL, GABRIEL, [(MARCUS, "write"), (SOFIA, "write"), (AALIYA, "admin")]),
3030 (REPO_MODAL_JAZZ, GABRIEL, [(MARCUS, "write"), (CHEN, "write")]),
3031 (REPO_AMBIENT, SOFIA, [(YUKI, "write"), (PIERRE, "write"), (GABRIEL, "admin")]),
3032 (REPO_AFROBEAT, AALIYA, [(FATOU, "write"), (GABRIEL, "write")]),
3033 (REPO_MICROTONAL, CHEN, [(PIERRE, "write")]),
3034 (REPO_DRUM_MACHINE, FATOU, [(AALIYA, "write"), (MARCUS, "write")]),
3035 (REPO_CHANSON, PIERRE, [(SOFIA, "write")]),
3036 (REPO_GRANULAR, YUKI, [(SOFIA, "write"), (CHEN, "write")]),
3037 (REPO_FUNK_SUITE, MARCUS, [(GABRIEL, "write"), (AALIYA, "admin")]),
3038 (REPO_JAZZ_TRIO, MARCUS, [(GABRIEL, "write")]),
3039 (REPO_NEO_BAROQUE, GABRIEL, [(PIERRE, "write"), (CHEN, "write")]),
3040 (REPO_JAZZ_CHOPIN, AALIYA, [(GABRIEL, "write"), (MARCUS, "write")]),
3041 (REPO_COMMUNITY, GABRIEL, [(SOFIA, "admin"), (MARCUS, "write"), (YUKI, "write"),
3042 (AALIYA, "write"), (CHEN, "write"), (FATOU, "write"), (PIERRE, "write")]),
3043 ]
3044 collab_count = 0
3045 for repo_id, owner_uid, collab_list in _COLLAB_CONFIGS:
3046 for collab_uid, perm in collab_list:
3047 accepted = _now(days=abs(hash(collab_uid + repo_id)) % 20 + 1)
3048 db.add(MusehubCollaborator(
3049 id=_uid(f"collab-{repo_id}-{collab_uid}"),
3050 repo_id=repo_id,
3051 user_id=collab_uid,
3052 permission=perm,
3053 invited_by=owner_uid,
3054 invited_at=_now(days=abs(hash(collab_uid + repo_id)) % 20 + 3),
3055 accepted_at=accepted,
3056 ))
3057 collab_count += 1
3058 print(f" ✅ Collaborators: {collab_count}")
3059
3060 await db.flush()
3061
3062 # ── 8c. Stash (shelved in-progress work per user/repo) ────────────────────
3063 _STASH_MESSAGES: list[str] = [
3064 "WIP: Rhodes chord voicings — not ready to commit",
3065 "Experiment: tritone sub on IV chord — might revert",
3066 "Sketching counter-melody — needs more work",
3067 "Bass line draft — 3-against-4 groove not locked yet",
3068 "Drum fills experiment — comparing two approaches",
3069 "Harmony sketch: parallel 10ths — maybe too classical?",
3070 "Tempo map idea: ritardando at bar 36 — not sure yet",
3071 ]
3072 _STASH_CONFIGS: list[tuple[str, str, str]] = [
3073 # (repo_id, user_id, branch)
3074 (REPO_NEO_SOUL, GABRIEL, "feat/counter-melody"),
3075 (REPO_AMBIENT, SOFIA, "experiment/drone-layer"),
3076 (REPO_AFROBEAT, AALIYA, "feat/brass-section"),
3077 (REPO_FUNK_SUITE, MARCUS, "experiment/fretless-bass"),
3078 (REPO_MICROTONAL, CHEN, "feat/spectral-harmony"),
3079 (REPO_GRANULAR, YUKI, "experiment/formant-filter"),
3080 (REPO_CHANSON, PIERRE, "feat/coda-extension"),
3081 (REPO_COMMUNITY, GABRIEL, "collab/all-genre-finale"),
3082 (REPO_MODAL_JAZZ, GABRIEL, "feat/mcoy-voicings"),
3083 (REPO_NEO_BAROQUE, GABRIEL, "experiment/fugue-development"),
3084 ]
3085 stash_count = 0
3086 stash_entry_count = 0
3087 for si, (repo_id, user_id, branch) in enumerate(_STASH_CONFIGS):
3088 stash_id = _uid(f"stash-{repo_id}-{user_id}-{si}")
3089 msg = _STASH_MESSAGES[si % len(_STASH_MESSAGES)]
3090 is_applied = si % 5 == 0 # Every 5th stash has been popped
3091 applied_at = _now(days=1) if is_applied else None
3092 db.add(MusehubStash(
3093 id=stash_id,
3094 repo_id=repo_id,
3095 user_id=user_id,
3096 branch=branch,
3097 message=msg,
3098 is_applied=is_applied,
3099 created_at=_now(days=si + 2),
3100 applied_at=applied_at,
3101 ))
3102 stash_count += 1
3103 # 2-4 MIDI file entries per stash
3104 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
3105 stash_tracks = REPO_TRACKS.get(rkey, REPO_TRACKS["neo-soul"])
3106 n_entries = 2 + (si % 3)
3107 for ei, (role, fpath) in enumerate(stash_tracks[:n_entries]):
3108 obj_id = f"sha256:{_sha(f'stash-obj-{stash_id}-{role}')}"
3109 db.add(MusehubStashEntry(
3110 id=_uid(f"stash-entry-{stash_id}-{ei}"),
3111 stash_id=stash_id,
3112 path=f"tracks/{fpath}",
3113 object_id=obj_id,
3114 position=ei,
3115 ))
3116 stash_entry_count += 1
3117 print(f" ✅ Stash: {stash_count} stashes, {stash_entry_count} entries")
3118
3119 await db.flush()
3120
3121 # ── 9. Stars (cross-repo) ─────────────────────────────────────
3122 # Every community user stars 5-10 repos; genre/composer archives also get
3123 # starred by community users who inspired or forked from them. 50+ total.
3124 star_pairs = [
3125 # Community repos — broad community engagement
3126 (SOFIA, REPO_NEO_SOUL, 20), (MARCUS, REPO_NEO_SOUL, 18),
3127 (YUKI, REPO_NEO_SOUL, 15), (AALIYA, REPO_NEO_SOUL, 12),
3128 (CHEN, REPO_NEO_SOUL, 10), (FATOU, REPO_NEO_SOUL, 8),
3129 (PIERRE, REPO_NEO_SOUL, 6),
3130
3131 (GABRIEL, REPO_AMBIENT, 14), (MARCUS, REPO_AMBIENT, 12),
3132 (YUKI, REPO_AMBIENT, 10), (AALIYA, REPO_AMBIENT, 9),
3133 (CHEN, REPO_AMBIENT, 7), (FATOU, REPO_AMBIENT, 5),
3134 (PIERRE, REPO_AMBIENT, 3),
3135
3136 (GABRIEL, REPO_AFROBEAT, 8), (SOFIA, REPO_AFROBEAT, 7),
3137 (MARCUS, REPO_AFROBEAT, 6), (YUKI, REPO_AFROBEAT, 5),
3138 (CHEN, REPO_AFROBEAT, 4), (PIERRE, REPO_AFROBEAT, 3),
3139
3140 (GABRIEL, REPO_FUNK_SUITE, 12), (SOFIA, REPO_FUNK_SUITE, 10),
3141 (AALIYA, REPO_FUNK_SUITE, 9), (CHEN, REPO_FUNK_SUITE, 7),
3142 (FATOU, REPO_FUNK_SUITE, 5),
3143
3144 (GABRIEL, REPO_MODAL_JAZZ, 11), (SOFIA, REPO_MODAL_JAZZ, 8),
3145 (MARCUS, REPO_MODAL_JAZZ, 6), (AALIYA, REPO_MODAL_JAZZ, 4),
3146
3147 (GABRIEL, REPO_JAZZ_TRIO, 9), (SOFIA, REPO_JAZZ_TRIO, 7),
3148 (AALIYA, REPO_JAZZ_TRIO, 5), (CHEN, REPO_JAZZ_TRIO, 3),
3149
3150 (GABRIEL, REPO_DRUM_MACHINE, 6), (MARCUS, REPO_DRUM_MACHINE, 4),
3151 (GABRIEL, REPO_GRANULAR, 5), (MARCUS, REPO_GRANULAR, 3),
3152 (GABRIEL, REPO_CHANSON, 4), (SOFIA, REPO_CHANSON, 3),
3153 (GABRIEL, REPO_MICROTONAL, 3), (SOFIA, REPO_MICROTONAL, 2),
3154
3155 # Genre/composer archive repos starred by community users who draw
3156 # inspiration from or fork into them
3157 (GABRIEL, REPO_GOLDBERG, 25), (SOFIA, REPO_GOLDBERG, 22),
3158 (MARCUS, REPO_GOLDBERG, 18), (AALIYA, REPO_GOLDBERG, 15),
3159 (CHEN, REPO_GOLDBERG, 12), (FATOU, REPO_GOLDBERG, 9),
3160 (PIERRE, REPO_GOLDBERG, 7), (YUKI, REPO_GOLDBERG, 5),
3161
3162 (GABRIEL, REPO_WTC, 24), (SOFIA, REPO_WTC, 20),
3163 (CHEN, REPO_WTC, 16), (MARCUS, REPO_WTC, 13),
3164 (YUKI, REPO_WTC, 10), (PIERRE, REPO_WTC, 6),
3165
3166 (GABRIEL, REPO_NOCTURNES, 22), (AALIYA, REPO_NOCTURNES, 18),
3167 (SOFIA, REPO_NOCTURNES, 15), (CHEN, REPO_NOCTURNES, 11),
3168 (MARCUS, REPO_NOCTURNES, 8), (YUKI, REPO_NOCTURNES, 5),
3169
3170 (MARCUS, REPO_MAPLE_LEAF, 20), (GABRIEL, REPO_MAPLE_LEAF, 17),
3171 (AALIYA, REPO_MAPLE_LEAF, 13), (FATOU, REPO_MAPLE_LEAF, 9),
3172 (PIERRE, REPO_MAPLE_LEAF, 6),
3173
3174 (CHEN, REPO_CIN_STRINGS, 19), (GABRIEL, REPO_CIN_STRINGS, 15),
3175 (SOFIA, REPO_CIN_STRINGS, 12), (YUKI, REPO_CIN_STRINGS, 8),
3176 (MARCUS, REPO_CIN_STRINGS, 5),
3177
3178 # Community genre-fusion repos (created as forks of composer archives)
3179 (SOFIA, REPO_NEO_BAROQUE, 14), (MARCUS, REPO_NEO_BAROQUE, 11),
3180 (CHEN, REPO_NEO_BAROQUE, 8), (YUKI, REPO_NEO_BAROQUE, 5),
3181 (PIERRE, REPO_NEO_BAROQUE, 3),
3182
3183 (GABRIEL, REPO_JAZZ_CHOPIN, 12), (MARCUS, REPO_JAZZ_CHOPIN, 9),
3184 (SOFIA, REPO_JAZZ_CHOPIN, 7), (CHEN, REPO_JAZZ_CHOPIN, 4),
3185
3186 (GABRIEL, REPO_RAGTIME_EDM, 10), (AALIYA, REPO_RAGTIME_EDM, 8),
3187 (FATOU, REPO_RAGTIME_EDM, 6), (YUKI, REPO_RAGTIME_EDM, 4),
3188
3189 (GABRIEL, REPO_FILM_SCORE, 9), (SOFIA, REPO_FILM_SCORE, 7),
3190 (MARCUS, REPO_FILM_SCORE, 5), (AALIYA, REPO_FILM_SCORE, 3),
3191
3192 (GABRIEL, REPO_COMMUNITY, 8), (SOFIA, REPO_COMMUNITY, 6),
3193 (MARCUS, REPO_COMMUNITY, 5), (AALIYA, REPO_COMMUNITY, 4),
3194 (CHEN, REPO_COMMUNITY, 3), (FATOU, REPO_COMMUNITY, 2),
3195 (PIERRE, REPO_COMMUNITY, 1),
3196 ]
3197 for user_id, repo_id, days in star_pairs:
3198 db.add(MusehubStar(repo_id=repo_id, user_id=user_id,
3199 created_at=_now(days=days)))
3200 print(f" ✅ Stars: {len(star_pairs)}")
3201
3202 await db.flush()
3203
3204 # ── 10. Comments ─────────────────────────────────────────────
3205 COMMENT_BODIES = [
3206 "This groove is incredible — the 3-against-4 polyrhythm is exactly what this track needed.",
3207 "Love the Rhodes voicings here. Upper-structure triads give it that sophisticated neo-soul feel.",
3208 "The humanization really helped. Feels much more like a live performance now.",
3209 "I think the bridge needs more harmonic tension. The IV-I resolution is too settled.",
3210 "That trumpet counter-melody is stunning. It perfectly answers the Rhodes line.",
3211 "Could we push the bass a bit more? It's sitting a little behind the kick drum.",
3212 "The string pizzicato in the verse is a beautiful subtle touch.",
3213 "I'm not sure about the guitar scratch — feels a bit busy with the Rhodes.",
3214 "This is really coming together. The dynamic arc from intro to chorus is perfect.",
3215 "The call-and-response between horns and strings is very Quincy Jones.",
3216 ]
3217
3218 comment_count = 0
3219 for r in REPOS[:6]: # Comments on first 6 repos
3220 repo_id = r["repo_id"]
3221 commits = all_commits.get(repo_id, [])
3222 if not commits:
3223 continue
3224 # Comments on latest 3 commits
3225 for i, commit in enumerate(commits[-3:]):
3226 for j in range(3):
3227 body = COMMENT_BODIES[(i * 3 + j) % len(COMMENT_BODIES)]
3228 author_ids = [GABRIEL, SOFIA, MARCUS, YUKI, AALIYA, CHEN, FATOU, PIERRE]
3229 author = [u[1] for u in USERS if u[0] == author_ids[(i + j + hash(repo_id)) % len(author_ids)]][0]
3230 comment_id = _uid(f"comment-{repo_id}-{i}-{j}")
3231 db.add(MusehubComment(
3232 comment_id=comment_id,
3233 repo_id=repo_id,
3234 target_type="commit",
3235 target_id=commit["commit_id"],
3236 author=author,
3237 body=body,
3238 parent_id=None,
3239 created_at=_now(days=2, hours=i * 3 + j),
3240 ))
3241 comment_count += 1
3242 # Add a reply to first comment on each commit
3243 if j == 0:
3244 reply_author = [u[1] for u in USERS if u[1] != author][0]
3245 db.add(MusehubComment(
3246 comment_id=_uid(f"reply-{repo_id}-{i}-{j}"),
3247 repo_id=repo_id,
3248 target_type="commit",
3249 target_id=commit["commit_id"],
3250 author=reply_author,
3251 body="Totally agree — this part really elevates the whole track.",
3252 parent_id=comment_id,
3253 created_at=_now(days=1, hours=i * 2),
3254 ))
3255 comment_count += 1
3256 print(f" ✅ Comments: {comment_count}")
3257
3258 await db.flush()
3259
3260 # ── 11. Reactions ─────────────────────────────────────────────
3261 # All 8 emoji types from the spec: 👍❤️🎵🔥🎹👏🤔😢. 200+ total across
3262 # both community and genre-archive repos.
3263 EMOJIS = ["👍", "❤️", "🎵", "🔥", "🎹", "👏", "🤔", "😢"]
3264 reaction_count = 0
3265 all_community_users = [GABRIEL, SOFIA, MARCUS, YUKI, AALIYA, CHEN, FATOU, PIERRE]
3266 # Community repos — all 8 users react to last 5 commits on first 6 repos
3267 for r in REPOS[:6]:
3268 repo_id = r["repo_id"]
3269 commits = all_commits.get(repo_id, [])
3270 if not commits:
3271 continue
3272 for commit in commits[-5:]:
3273 for i, uid in enumerate(all_community_users):
3274 emoji = EMOJIS[i % len(EMOJIS)]
3275 try:
3276 db.add(MusehubReaction(
3277 reaction_id=_uid(f"reaction-{repo_id}-{commit['commit_id'][:8]}-{uid}"),
3278 repo_id=repo_id,
3279 target_type="commit",
3280 target_id=commit["commit_id"],
3281 user_id=uid,
3282 emoji=emoji,
3283 created_at=_now(days=1),
3284 ))
3285 reaction_count += 1
3286 except Exception:
3287 pass
3288 # Genre-archive repos — community users react to last 3 commits, rotating
3289 # through all emoji types to ensure full coverage
3290 genre_reaction_repos = [
3291 REPO_GOLDBERG, REPO_WTC, REPO_NOCTURNES, REPO_MAPLE_LEAF, REPO_CIN_STRINGS,
3292 ]
3293 for repo_id in genre_reaction_repos:
3294 commits = all_commits.get(repo_id, [])
3295 if not commits:
3296 continue
3297 for ci, commit in enumerate(commits[-3:]):
3298 for ui, uid in enumerate(all_community_users):
3299 emoji = EMOJIS[(ci * len(all_community_users) + ui) % len(EMOJIS)]
3300 try:
3301 db.add(MusehubReaction(
3302 reaction_id=_uid(f"reaction-g-{repo_id[:12]}-{commit['commit_id'][:8]}-{uid}"),
3303 repo_id=repo_id,
3304 target_type="commit",
3305 target_id=commit["commit_id"],
3306 user_id=uid,
3307 emoji=emoji,
3308 created_at=_now(days=2),
3309 ))
3310 reaction_count += 1
3311 except Exception:
3312 pass
3313 print(f" ✅ Reactions: {reaction_count}")
3314
3315 await db.flush()
3316
3317 # ── 12. Follows (social graph) ────────────────────────────────
3318 # Rich bidirectional follow graph: gabriel↔sofia, marcus↔fatou,
3319 # yuki↔chen, aaliya↔pierre, plus composer-to-community asymmetric follows.
3320 # 60+ total, preserving all prior pairs.
3321 follow_pairs = [
3322 # Original follows — gabriel is the hub (everyone follows him)
3323 (SOFIA, GABRIEL), (MARCUS, GABRIEL), (YUKI, GABRIEL),
3324 (AALIYA, GABRIEL), (CHEN, GABRIEL), (FATOU, GABRIEL),
3325 (PIERRE, GABRIEL),
3326
3327 # Bidirectional close collaborator pairs (symmetric)
3328 (GABRIEL, SOFIA), (SOFIA, GABRIEL), # already added above, deduped by _uid
3329 (MARCUS, FATOU), (FATOU, MARCUS),
3330 (YUKI, CHEN), (CHEN, YUKI),
3331 (AALIYA, PIERRE), (PIERRE, AALIYA),
3332
3333 # Cross-community follows
3334 (GABRIEL, MARCUS), (SOFIA, MARCUS), (AALIYA, MARCUS),
3335 (GABRIEL, YUKI), (SOFIA, YUKI),
3336 (GABRIEL, AALIYA), (MARCUS, AALIYA),
3337 (GABRIEL, CHEN),
3338 (GABRIEL, FATOU), (AALIYA, FATOU),
3339 (GABRIEL, PIERRE), (SOFIA, PIERRE),
3340 (MARCUS, SOFIA), (PIERRE, SOFIA),
3341 (MARCUS, YUKI), (FATOU, YUKI),
3342 (PIERRE, MARCUS), (YUKI, MARCUS),
3343 (CHEN, MARCUS), (FATOU, SOFIA),
3344 (YUKI, AALIYA), (CHEN, AALIYA), (FATOU, AALIYA),
3345 (MARCUS, CHEN), (AALIYA, CHEN), (PIERRE, CHEN),
3346 (SOFIA, FATOU), (CHEN, FATOU), (PIERRE, FATOU),
3347 (YUKI, PIERRE), (MARCUS, PIERRE), (CHEN, PIERRE),
3348 (SOFIA, CHEN), (AALIYA, YUKI), (FATOU, CHEN),
3349
3350 # Community users follow composer archive maintainers
3351 (GABRIEL, BACH), (SOFIA, BACH), (MARCUS, BACH),
3352 (CHEN, BACH), (AALIYA, BACH), (YUKI, BACH),
3353 (GABRIEL, CHOPIN), (AALIYA, CHOPIN), (SOFIA, CHOPIN),
3354 (MARCUS, SCOTT_JOPLIN), (GABRIEL, SCOTT_JOPLIN),
3355 (FATOU, SCOTT_JOPLIN),
3356 (CHEN, KEVIN_MACLEOD), (GABRIEL, KEVIN_MACLEOD),
3357 ]
3358 # Deduplicate pairs so _uid collisions never cause constraint errors
3359 seen_follows: set[tuple[str, str]] = set()
3360 deduped_follows = []
3361 for pair in follow_pairs:
3362 if pair not in seen_follows:
3363 seen_follows.add(pair)
3364 deduped_follows.append(pair)
3365 for follower, followee in deduped_follows:
3366 db.add(MusehubFollow(
3367 follow_id=_uid(f"follow-{follower}-{followee}"),
3368 follower_id=follower,
3369 followee_id=followee,
3370 created_at=_now(days=15),
3371 ))
3372 print(f" ✅ Follows: {len(deduped_follows)}")
3373
3374 await db.flush()
3375
3376 # ── 13. Watches ───────────────────────────────────────────────
3377 # 60+ total: community users watch each other's repos and the composer
3378 # archive repos they draw inspiration from.
3379 watch_pairs = [
3380 # Community repo watches
3381 (GABRIEL, REPO_AMBIENT), (GABRIEL, REPO_AFROBEAT), (GABRIEL, REPO_FUNK_SUITE),
3382 (GABRIEL, REPO_JAZZ_TRIO), (GABRIEL, REPO_GRANULAR), (GABRIEL, REPO_CHANSON),
3383 (GABRIEL, REPO_MICROTONAL), (GABRIEL, REPO_DRUM_MACHINE),
3384
3385 (SOFIA, REPO_NEO_SOUL), (SOFIA, REPO_FUNK_SUITE), (SOFIA, REPO_CHANSON),
3386 (SOFIA, REPO_MODAL_JAZZ), (SOFIA, REPO_AFROBEAT), (SOFIA, REPO_MICROTONAL),
3387
3388 (MARCUS, REPO_NEO_SOUL), (MARCUS, REPO_AMBIENT), (MARCUS, REPO_AFROBEAT),
3389 (MARCUS, REPO_MODAL_JAZZ), (MARCUS, REPO_JAZZ_TRIO), (MARCUS, REPO_FUNK_SUITE),
3390
3391 (YUKI, REPO_AMBIENT), (YUKI, REPO_GRANULAR), (YUKI, REPO_MICROTONAL),
3392 (YUKI, REPO_NEO_SOUL), (YUKI, REPO_DRUM_MACHINE),
3393
3394 (AALIYA, REPO_NEO_SOUL), (AALIYA, REPO_AFROBEAT), (AALIYA, REPO_FUNK_SUITE),
3395 (AALIYA, REPO_MODAL_JAZZ), (AALIYA, REPO_JAZZ_TRIO), (AALIYA, REPO_CHANSON),
3396
3397 (CHEN, REPO_MICROTONAL), (CHEN, REPO_AMBIENT), (CHEN, REPO_GRANULAR),
3398 (CHEN, REPO_MODAL_JAZZ), (CHEN, REPO_NEO_SOUL), (CHEN, REPO_DRUM_MACHINE),
3399
3400 (FATOU, REPO_AFROBEAT), (FATOU, REPO_DRUM_MACHINE),(FATOU, REPO_FUNK_SUITE),
3401 (FATOU, REPO_NEO_SOUL), (FATOU, REPO_MODAL_JAZZ), (FATOU, REPO_GRANULAR),
3402
3403 (PIERRE, REPO_CHANSON), (PIERRE, REPO_AMBIENT), (PIERRE, REPO_NEO_SOUL),
3404 (PIERRE, REPO_MODAL_JAZZ), (PIERRE, REPO_MICROTONAL),
3405
3406 # Composer archive repo watches — community users watching source material
3407 (GABRIEL, REPO_GOLDBERG), (GABRIEL, REPO_WTC), (GABRIEL, REPO_NOCTURNES),
3408 (GABRIEL, REPO_MAPLE_LEAF), (GABRIEL, REPO_CIN_STRINGS),
3409
3410 (SOFIA, REPO_GOLDBERG), (SOFIA, REPO_WTC), (SOFIA, REPO_NOCTURNES),
3411 (MARCUS, REPO_MAPLE_LEAF), (MARCUS, REPO_GOLDBERG), (MARCUS, REPO_WTC),
3412 (AALIYA, REPO_NOCTURNES), (AALIYA, REPO_MAPLE_LEAF),
3413 (CHEN, REPO_CIN_STRINGS), (CHEN, REPO_GOLDBERG),
3414 (YUKI, REPO_WTC), (FATOU, REPO_MAPLE_LEAF), (PIERRE, REPO_NOCTURNES),
3415
3416 # Genre-fusion community repos
3417 (SOFIA, REPO_NEO_BAROQUE), (MARCUS, REPO_NEO_BAROQUE),
3418 (GABRIEL, REPO_JAZZ_CHOPIN), (MARCUS, REPO_RAGTIME_EDM),
3419 (GABRIEL, REPO_FILM_SCORE), (SOFIA, REPO_FILM_SCORE),
3420 (GABRIEL, REPO_COMMUNITY), (AALIYA, REPO_COMMUNITY),
3421 ]
3422 for user_id, repo_id in watch_pairs:
3423 db.add(MusehubWatch(
3424 watch_id=_uid(f"watch-{user_id}-{repo_id}"),
3425 user_id=user_id,
3426 repo_id=repo_id,
3427 created_at=_now(days=10),
3428 ))
3429 print(f" ✅ Watches: {len(watch_pairs)}")
3430
3431 await db.flush()
3432
3433 # ── 14. Notifications ─────────────────────────────────────────
3434 # 15-25 unread per user, all event types, all 8 users. 15-25 unread means
3435 # we create 20 notifications per user with the first 5 marked as read and
3436 # the remaining 15 unread — satisfying the "15-25 unread" spec.
3437 EVENT_TYPES = [
3438 "comment", "pr_opened", "pr_merged", "issue_opened", "new_commit",
3439 "new_follower", "star", "fork", "release", "watch",
3440 ]
3441 NOTIFS_PER_USER = 20 # 5 read + 15 unread = 15 unread per user
3442 READ_THRESHOLD = 5 # first N are read
3443 all_repos_flat = [r["repo_id"] for r in (list(REPOS) + list(GENRE_REPOS))]
3444 notif_count = 0
3445 for i, (uid, uname, _) in enumerate(USERS):
3446 for j in range(NOTIFS_PER_USER):
3447 actor_user = USERS[(i + j + 1) % len(USERS)]
3448 db.add(MusehubNotification(
3449 notif_id=_uid(f"notif2-{uid}-{j}"),
3450 recipient_id=uid,
3451 event_type=EVENT_TYPES[j % len(EVENT_TYPES)],
3452 repo_id=all_repos_flat[(i + j) % len(all_repos_flat)],
3453 actor=actor_user[1],
3454 payload={"message": f"Notification {j + 1} for {uname}"},
3455 is_read=j < READ_THRESHOLD,
3456 created_at=_now(days=j),
3457 ))
3458 notif_count += 1
3459 print(f" ✅ Notifications: {notif_count} ({NOTIFS_PER_USER - READ_THRESHOLD} unread per user)")
3460
3461 await db.flush()
3462
3463 # ── 15. Forks ─────────────────────────────────────────────────
3464 # Original community-to-community forks
3465 db.add(MusehubFork(
3466 fork_id=_uid("fork-neo-soul-marcus"),
3467 source_repo_id=REPO_NEO_SOUL,
3468 fork_repo_id=REPO_NEO_SOUL_FORK,
3469 forked_by="marcus",
3470 created_at=_now(days=10),
3471 ))
3472 db.add(MusehubFork(
3473 fork_id=_uid("fork-ambient-yuki"),
3474 source_repo_id=REPO_AMBIENT,
3475 fork_repo_id=REPO_AMBIENT_FORK,
3476 forked_by="yuki",
3477 created_at=_now(days=5),
3478 ))
3479 # Genre-archive → community forks: each community repo that remixes a
3480 # composer's archive is modelled as a fork of the canonical archive repo.
3481 # marcus/ragtime-edm ← scott_joplin/maple-leaf-rag
3482 db.add(MusehubFork(
3483 fork_id=_uid("fork-maple-leaf-marcus"),
3484 source_repo_id=REPO_MAPLE_LEAF,
3485 fork_repo_id=REPO_RAGTIME_EDM,
3486 forked_by="marcus",
3487 created_at=_now(days=30),
3488 ))
3489 # aaliya/jazz-chopin ← chopin/nocturnes
3490 db.add(MusehubFork(
3491 fork_id=_uid("fork-nocturnes-aaliya"),
3492 source_repo_id=REPO_NOCTURNES,
3493 fork_repo_id=REPO_JAZZ_CHOPIN,
3494 forked_by="aaliya",
3495 created_at=_now(days=28),
3496 ))
3497 # gabriel/neo-baroque ← bach/goldberg-variations
3498 db.add(MusehubFork(
3499 fork_id=_uid("fork-goldberg-gabriel-neobaroque"),
3500 source_repo_id=REPO_GOLDBERG,
3501 fork_repo_id=REPO_NEO_BAROQUE,
3502 forked_by="gabriel",
3503 created_at=_now(days=25),
3504 ))
3505 # chen/film-score ← kevin_macleod/cinematic-strings
3506 db.add(MusehubFork(
3507 fork_id=_uid("fork-cinstrings-chen"),
3508 source_repo_id=REPO_CIN_STRINGS,
3509 fork_repo_id=REPO_FILM_SCORE,
3510 forked_by="chen",
3511 created_at=_now(days=22),
3512 ))
3513 # gabriel/community-collab ← bach/goldberg-variations
3514 db.add(MusehubFork(
3515 fork_id=_uid("fork-goldberg-gabriel-community"),
3516 source_repo_id=REPO_GOLDBERG,
3517 fork_repo_id=REPO_COMMUNITY,
3518 forked_by="gabriel",
3519 created_at=_now(days=20),
3520 ))
3521 print(" ✅ Forks: 7")
3522
3523 await db.flush()
3524
3525 # ── 16. View events (analytics) ───────────────────────────────
3526 # 1000+ total across all repos (community + genre archives). Each repo
3527 # receives 30 days of daily view fingerprints; active repos get up to 10
3528 # unique viewers per day, quieter repos get fewer.
3529 view_count = 0
3530 view_repos = list(REPOS) + list(GENRE_REPOS)
3531 for r in view_repos:
3532 repo_id = r["repo_id"]
3533 star_count = r.get("star_count", 5)
3534 for day_offset in range(30):
3535 date_str = (_now(days=day_offset)).strftime("%Y-%m-%d")
3536 viewers = max(star_count // 3 + 1, 3) # at least 3 unique viewers/day
3537 for v in range(min(viewers, 10)):
3538 try:
3539 db.add(MusehubViewEvent(
3540 view_id=_uid(f"view-{repo_id}-{day_offset}-{v}"),
3541 repo_id=repo_id,
3542 viewer_fingerprint=_sha(f"viewer-{repo_id}-{v}"),
3543 event_date=date_str,
3544 created_at=_now(days=day_offset),
3545 ))
3546 view_count += 1
3547 except Exception:
3548 pass
3549 print(f" ✅ View events: {view_count}")
3550
3551 await db.flush()
3552
3553 # ── 17. Download events ───────────────────────────────────────
3554 # 5-25 downloads per release tag across all repos. Using release_tags
3555 # (built in step 7) so every release gets at least 5 unique downloaders.
3556 all_user_ids = [u[0] for u in USERS]
3557 dl_count = 0
3558 dl_repos = list(REPOS) + list(GENRE_REPOS)
3559 for r in dl_repos:
3560 repo_id = r["repo_id"]
3561 tags = release_tags.get(repo_id, ["main"])
3562 if not tags:
3563 tags = ["main"]
3564 for ti, tag in enumerate(tags):
3565 # 5-15 downloads per release depending on tag index (older = more)
3566 n_downloads = max(5, 15 - ti * 2)
3567 for i in range(n_downloads):
3568 db.add(MusehubDownloadEvent(
3569 dl_id=_uid(f"dl2-{repo_id}-{tag}-{i}"),
3570 repo_id=repo_id,
3571 ref=tag,
3572 downloader_id=all_user_ids[i % len(all_user_ids)],
3573 created_at=_now(days=ti * 7 + i),
3574 ))
3575 dl_count += 1
3576 print(f" ✅ Download events: {dl_count}")
3577
3578 # ── 17b. Webhooks + deliveries (1-3/repo, 10-20 deliveries each) ──────────
3579 WEBHOOK_CONFIGS: list[tuple[str, list[str]]] = [
3580 # (url_suffix, events)
3581 ("push", ["push"]),
3582 ("pr", ["pull_request", "push"]),
3583 ("release", ["release", "push", "pull_request"]),
3584 ]
3585 # Delivery outcome patterns: (response_status, success)
3586 # Mix of 200 OK, 500 server error, and 0 timeout
3587 DELIVERY_OUTCOMES: list[tuple[int, bool, str]] = [
3588 (200, True, '{"status": "ok"}'),
3589 (200, True, '{"accepted": true}'),
3590 (200, True, '{"queued": true}'),
3591 (500, False, '{"error": "Internal Server Error"}'),
3592 (500, False, '{"error": "Service unavailable"}'),
3593 (0, False, ""), # timeout — no response
3594 (200, True, '{"ok": 1}'),
3595 (404, False, '{"error": "Not Found"}'),
3596 (200, True, '{"received": true}'),
3597 (0, False, ""), # timeout
3598 ]
3599 WEBHOOK_EVENT_TYPES = ["push", "pull_request", "release", "issue", "comment"]
3600
3601 webhook_count = 0
3602 delivery_count = 0
3603 for r in REPOS[:10]:
3604 repo_id = r["repo_id"]
3605 # 1-3 webhooks per repo based on repo index
3606 repo_idx = REPOS.index(r)
3607 n_webhooks = 1 + (repo_idx % 3)
3608 for wh_i in range(n_webhooks):
3609 url_suffix, events = WEBHOOK_CONFIGS[wh_i % len(WEBHOOK_CONFIGS)]
3610 wh_id = _uid(f"webhook-{repo_id}-{wh_i}")
3611 db.add(MusehubWebhook(
3612 webhook_id=wh_id,
3613 repo_id=repo_id,
3614 url=f"https://hooks.example.com/{r['owner']}/{r['slug']}/{url_suffix}",
3615 events=events,
3616 secret=_sha(f"secret-{repo_id}-{wh_i}")[:32],
3617 active=True,
3618 created_at=_now(days=r["days_ago"] - 5),
3619 ))
3620 webhook_count += 1
3621 # 10-20 deliveries per webhook
3622 n_deliveries = 10 + (abs(hash(wh_id)) % 11)
3623 for d_i in range(n_deliveries):
3624 outcome_idx = (abs(hash(wh_id)) + d_i) % len(DELIVERY_OUTCOMES)
3625 status, success, resp_body = DELIVERY_OUTCOMES[outcome_idx]
3626 evt = WEBHOOK_EVENT_TYPES[d_i % len(WEBHOOK_EVENT_TYPES)]
3627 db.add(MusehubWebhookDelivery(
3628 delivery_id=_uid(f"wh-delivery-{wh_id}-{d_i}"),
3629 webhook_id=wh_id,
3630 event_type=evt,
3631 payload=f'{{"event": "{evt}", "repo": "{repo_id}", "seq": {d_i}}}',
3632 attempt=1 + (d_i % 3),
3633 success=success,
3634 response_status=status,
3635 response_body=resp_body,
3636 delivered_at=_now(days=r["days_ago"] - d_i, hours=d_i % 24),
3637 ))
3638 delivery_count += 1
3639 print(f" ✅ Webhooks: {webhook_count} Deliveries: {delivery_count}")
3640
3641 await db.flush()
3642
3643 # ── 17c. Render Jobs (async audio render pipeline) ─────────────────────────
3644 # Creates completed, processing, and failed render jobs across repos.
3645 # Each job ties a commit to a set of generated MP3/piano-roll artifact IDs.
3646 _RENDER_STATUSES = ["completed", "completed", "completed", "processing", "failed"]
3647 render_job_count = 0
3648 for r in all_repos[:16]: # Community + most popular genre archive repos
3649 repo_id = r["repo_id"]
3650 commits = all_commits.get(repo_id, [])
3651 if not commits:
3652 continue
3653 # 2-3 render jobs per repo (latest commits)
3654 target_commits = commits[-3:]
3655 for ji, c in enumerate(target_commits):
3656 rj_cid: str = c["commit_id"]
3657 rj_status: str = _RENDER_STATUSES[(abs(hash(repo_id)) + ji) % len(_RENDER_STATUSES)]
3658 err_msg = "Storpheus timeout after 30s" if rj_status == "failed" else None
3659 mp3_ids = (
3660 [f"sha256:{_sha(f'mp3-{rj_cid}-{i}')}" for i in range(3)]
3661 if rj_status == "completed" else []
3662 )
3663 img_ids = (
3664 [f"sha256:{_sha(f'img-{rj_cid}-{i}')}" for i in range(2)]
3665 if rj_status == "completed" else []
3666 )
3667 try:
3668 db.add(MusehubRenderJob(
3669 render_job_id=_uid(f"rjob-{repo_id}-{rj_cid[:12]}"),
3670 repo_id=repo_id,
3671 commit_id=rj_cid,
3672 status=rj_status,
3673 error_message=err_msg,
3674 midi_count=len(REPO_TRACKS.get(REPO_KEY_MAP.get(repo_id, "neo-soul"),
3675 REPO_TRACKS["neo-soul"])),
3676 mp3_object_ids=mp3_ids,
3677 image_object_ids=img_ids,
3678 created_at=_now(days=ji + 1),
3679 updated_at=_now(days=ji),
3680 ))
3681 render_job_count += 1
3682 except Exception:
3683 pass # unique constraint on (repo_id, commit_id) — skip dupes
3684 print(f" ✅ Render jobs: {render_job_count}")
3685
3686 await db.flush()
3687
3688 # ── 17d. Activity Event Stream (musehub_events) ────────────────────────────
3689 # Captures the most recent activity on each repo: push, pr_open, pr_merge,
3690 # issue_open, issue_close, release, tag, session, fork, star events.
3691 _EVENT_TEMPLATES: list[tuple[str, str]] = [
3692 ("push", "pushed {n} commits to {branch}"),
3693 ("push", "force-pushed to {branch} (rebase)"),
3694 ("pr_open", "opened pull request: {title}"),
3695 ("pr_merge", "merged pull request into {branch}"),
3696 ("pr_close", "closed pull request without merging"),
3697 ("issue_open", "opened issue: {title}"),
3698 ("issue_close", "closed issue #{n}"),
3699 ("release", "published release {tag}"),
3700 ("tag", "created tag {tag} at {branch}"),
3701 ("session_start", "started recording session"),
3702 ("session_end", "ended recording session — {n} commits"),
3703 ("fork", "forked this repo"),
3704 ("star", "starred this repo"),
3705 ("branch_create", "created branch {branch}"),
3706 ]
3707 _EVENT_ACTORS_POOL = list(ALL_CONTRIBUTORS)
3708 event_count = 0
3709 for r in all_repos:
3710 repo_id = r["repo_id"]
3711 commits = all_commits.get(repo_id, [])
3712 owner = r["owner"]
3713 actor_pool = [owner] * 3 + _EVENT_ACTORS_POOL # Weight owner higher
3714 # Generate 8-15 events per repo spread over the last 60 days
3715 n_events = 8 + abs(hash(repo_id)) % 8
3716 for ei in range(n_events):
3717 tmpl_type, tmpl_body = _EVENT_TEMPLATES[ei % len(_EVENT_TEMPLATES)]
3718 actor = actor_pool[(abs(hash(repo_id)) + ei) % len(actor_pool)]
3719 n_val = (ei % 10) + 1
3720 branch = "main" if ei % 3 == 0 else f"feat/{r['slug'][:20]}-{ei}"
3721 tag = f"v{ei // 3 + 1}.0.{'0' if ei % 2 == 0 else '1'}"
3722 description = (
3723 tmpl_body
3724 .replace("{n}", str(n_val))
3725 .replace("{branch}", branch)
3726 .replace("{title}", f"Issue/PR title for event {ei}")
3727 .replace("{tag}", tag)
3728 )
3729 meta: dict[str, object] = {
3730 "actor": actor,
3731 "branch": branch,
3732 "commit_count": n_val,
3733 }
3734 if commits and ei < len(commits):
3735 meta["commit_id"] = commits[ei]["commit_id"]
3736 db.add(MusehubEvent(
3737 event_id=_uid(f"event-{repo_id}-{ei}"),
3738 repo_id=repo_id,
3739 event_type=tmpl_type,
3740 actor=actor,
3741 description=description,
3742 event_metadata=meta,
3743 created_at=_now(days=60 - ei * 4, hours=ei * 3 % 24),
3744 ))
3745 event_count += 1
3746 print(f" ✅ Events: {event_count}")
3747
3748 await db.flush()
3749
3750 # ── 18. Muse VCS — muse_objects, muse_snapshots, muse_commits, muse_tags ─
3751 #
3752 # Inserts content-addressed MIDI blobs, snapshot manifests, a proper DAG
3753 # of Muse commits (including merge commits), and the full tag taxonomy.
3754 #
3755 # Insertion order respects FK constraints:
3756 # muse_objects → muse_snapshots → muse_commits → muse_tags
3757 #
3758 muse_obj_count = 0
3759 muse_snap_count = 0
3760 muse_commit_count = 0
3761 muse_tag_count = 0
3762
3763 # Running objects pool so the same content can be deduplicated across
3764 # snapshots (object_ids that haven't changed reuse the same sha256).
3765 # Structure: repo_id → {filename: object_id}
3766 _prev_objects: dict[str, dict[str, str]] = {}
3767
3768 for r in REPOS:
3769 repo_id = r["repo_id"]
3770 hub_commits = all_commits.get(repo_id, [])
3771 if not hub_commits:
3772 continue
3773
3774 track_files = MUSE_VCS_FILES.get(repo_id, MUSE_VCS_FILES[REPO_AMBIENT])
3775 meta = MUSE_COMMIT_META.get(repo_id, MUSE_COMMIT_META[REPO_AMBIENT])
3776 is_rich = repo_id in MUSE_RICH_TAG_REPOS
3777
3778 prev_objects: dict[str, str] = {} # filename → object_id for this repo
3779 muse_commit_ids: list[str] = [] # ordered muse commit_ids for this repo
3780
3781 for i, hub_c in enumerate(hub_commits):
3782 snap_seed = f"snap-muse-{repo_id}-{i}"
3783 committed_at = hub_c["timestamp"]
3784
3785 # Build this commit's object set.
3786 # Every commit, ~2 files "change" (get fresh object_ids).
3787 # The rest reuse from the previous commit — simulating deduplication.
3788 changed_indices = {i % len(track_files), (i + 2) % len(track_files)}
3789 commit_objects: dict[str, str] = {}
3790
3791 for fi, (fname, base_size) in enumerate(track_files):
3792 if fi in changed_indices or fname not in prev_objects:
3793 # New or modified file → fresh content-addressed blob.
3794 obj_id = _sha(f"midi-{repo_id}-{fname}-v{i}")
3795 size = base_size + (i * 128) % 4096
3796 await db.execute(
3797 text(
3798 "INSERT INTO muse_objects (object_id, size_bytes, created_at)"
3799 " VALUES (:oid, :sz, :ca)"
3800 " ON CONFLICT (object_id) DO NOTHING"
3801 ),
3802 {"oid": obj_id, "sz": size, "ca": committed_at},
3803 )
3804 muse_obj_count += 1
3805 else:
3806 # Unchanged file → reuse previous object_id (deduplication).
3807 obj_id = prev_objects[fname]
3808 commit_objects[fname] = obj_id
3809
3810 prev_objects = commit_objects
3811
3812 # Snapshot — manifest maps track paths to object_ids.
3813 snapshot_id = _sha(snap_seed)
3814 manifest: dict[str, str] = {f"tracks/{fname}": oid for fname, oid in commit_objects.items()}
3815 await db.execute(
3816 text(
3817 "INSERT INTO muse_snapshots (snapshot_id, manifest, created_at)"
3818 " VALUES (:sid, :manifest, :ca)"
3819 " ON CONFLICT (snapshot_id) DO NOTHING"
3820 ),
3821 {"sid": snapshot_id, "manifest": json.dumps(manifest), "ca": committed_at},
3822 )
3823 muse_snap_count += 1
3824
3825 # Muse commit — derives its ID from snapshot + parent + message.
3826 parent_id: str | None = muse_commit_ids[-1] if muse_commit_ids else None
3827 # Merge commit every 7 commits (from commit 7 onward) — parent2 is the
3828 # commit from 5 positions back, simulating a merged feature branch.
3829 # Interval of 7 guarantees ≥5 merges per repo for repos with ≥35 commits.
3830 parent2_id: str | None = None
3831 if i >= 7 and i % 7 == 0 and len(muse_commit_ids) >= 6:
3832 parent2_id = muse_commit_ids[-6]
3833
3834 commit_id = _sha(f"muse-c-{snapshot_id}-{parent_id or ''}-{hub_c['message']}")
3835 await db.execute(
3836 text(
3837 "INSERT INTO muse_commits"
3838 " (commit_id, repo_id, branch, parent_commit_id, parent2_commit_id,"
3839 " snapshot_id, message, author, committed_at, created_at, metadata)"
3840 " VALUES"
3841 " (:cid, :rid, :branch, :pid, :p2id,"
3842 " :sid, :msg, :author, :cat, :cat, :meta)"
3843 " ON CONFLICT (commit_id) DO NOTHING"
3844 ),
3845 {
3846 "cid": commit_id,
3847 "rid": repo_id,
3848 "branch": hub_c["branch"],
3849 "pid": parent_id,
3850 "p2id": parent2_id,
3851 "sid": snapshot_id,
3852 "msg": hub_c["message"],
3853 "author": hub_c["author"],
3854 "cat": committed_at,
3855 "meta": json.dumps(meta),
3856 },
3857 )
3858 muse_commit_ids.append(commit_id)
3859 muse_commit_count += 1
3860
3861 # Tags: apply cycling taxonomy to every commit.
3862 # Rich repos get ALL taxonomy values; others get a representative subset.
3863 if is_rich:
3864 # Cycle through all 57 tag values across commits so every value appears.
3865 tag_val = _ALL_MUSE_TAGS[i % len(_ALL_MUSE_TAGS)]
3866 tag_vals = [tag_val]
3867 # Also add a second tag from a different category group.
3868 second_idx = (i + len(MUSE_EMOTION_TAGS)) % len(_ALL_MUSE_TAGS)
3869 if second_idx != i % len(_ALL_MUSE_TAGS):
3870 tag_vals.append(_ALL_MUSE_TAGS[second_idx])
3871 else:
3872 # Non-rich repos get one tag per commit drawn from a trimmed pool.
3873 _trimmed = MUSE_EMOTION_TAGS + MUSE_STAGE_TAGS + MUSE_GENRE_TAGS
3874 tag_vals = [_trimmed[i % len(_trimmed)]]
3875
3876 for tag_val in tag_vals:
3877 tag_id = _uid(f"muse-tag-{commit_id}-{tag_val}")
3878 await db.execute(
3879 text(
3880 "INSERT INTO muse_tags (tag_id, repo_id, commit_id, tag, created_at)"
3881 " VALUES (:tid, :rid, :cid, :tag, :ca)"
3882 " ON CONFLICT (tag_id) DO NOTHING"
3883 ),
3884 {"tid": tag_id, "rid": repo_id, "cid": commit_id,
3885 "tag": tag_val, "ca": committed_at},
3886 )
3887 muse_tag_count += 1
3888
3889 _prev_objects[repo_id] = prev_objects
3890
3891 # Ensure every tag taxonomy value appears at least once in REPO_NEO_SOUL.
3892 # Walk through ALL values and seed any that haven't been covered yet.
3893 if all_commits.get(REPO_NEO_SOUL):
3894 hub_commits_ns = all_commits[REPO_NEO_SOUL]
3895 muse_ids_ns: list[str] = []
3896 for i, hub_c in enumerate(hub_commits_ns):
3897 snap_seed = f"snap-muse-{REPO_NEO_SOUL}-{i}"
3898 snapshot_id = _sha(snap_seed)
3899 parent_id_ns: str | None = muse_ids_ns[-1] if muse_ids_ns else None
3900 commit_id_ns = _sha(f"muse-c-{snapshot_id}-{parent_id_ns or ''}-{hub_c['message']}")
3901 muse_ids_ns.append(commit_id_ns)
3902
3903 # Fetch existing tags for REPO_NEO_SOUL.
3904 result = await db.execute(
3905 text("SELECT tag FROM muse_tags WHERE repo_id = :rid"),
3906 {"rid": REPO_NEO_SOUL},
3907 )
3908 existing_tags: set[str] = {row[0] for row in result.fetchall()}
3909 missing_tags = [t for t in _ALL_MUSE_TAGS if t not in existing_tags]
3910
3911 for j, missing_tag in enumerate(missing_tags):
3912 commit_id_ns = muse_ids_ns[j % len(muse_ids_ns)]
3913 committed_at_ns = hub_commits_ns[j % len(hub_commits_ns)]["timestamp"]
3914 tag_id = _uid(f"muse-tag-fill-{REPO_NEO_SOUL}-{missing_tag}")
3915 await db.execute(
3916 text(
3917 "INSERT INTO muse_tags (tag_id, repo_id, commit_id, tag, created_at)"
3918 " VALUES (:tid, :rid, :cid, :tag, :ca)"
3919 " ON CONFLICT (tag_id) DO NOTHING"
3920 ),
3921 {"tid": tag_id, "rid": REPO_NEO_SOUL, "cid": commit_id_ns,
3922 "tag": missing_tag, "ca": committed_at_ns},
3923 )
3924 muse_tag_count += 1
3925
3926 await db.flush()
3927 print(f" ✅ Muse objects: {muse_obj_count} blobs")
3928 print(f" ✅ Muse snapshots: {muse_snap_count} manifests")
3929 print(f" ✅ Muse commits: {muse_commit_count} (DAG; includes merge commits)")
3930 print(f" ✅ Muse tags: {muse_tag_count} (full taxonomy)")
3931
3932 await db.flush()
3933
3934 # ── 20. Access Tokens (API key tracking) ──────────────────────────────────
3935 token_count = 0
3936 for uid, uname, _ in USERS:
3937 # 1-3 access tokens per user (active, revoked, expired)
3938 n_tokens = 1 + abs(hash(uid)) % 3
3939 for ti in range(n_tokens):
3940 is_revoked = ti > 0 and ti % 2 == 0
3941 db.add(AccessToken(
3942 id=_uid(f"token-{uid}-{ti}"),
3943 user_id=uid,
3944 token_hash=_sha(f"tok-{uid}-{ti}-secret"),
3945 expires_at=_now(days=-30 * ti) if is_revoked else _now(days=-365),
3946 revoked=is_revoked,
3947 created_at=_now(days=90 - ti * 15),
3948 ))
3949 token_count += 1
3950 print(f" ✅ Access tokens: {token_count}")
3951
3952 await db.flush()
3953
3954 await db.commit()
3955 print()
3956 _print_urls(all_commits, session_ids, pr_ids, release_tags)
3957
3958
3959 def _print_urls(
3960 all_commits: dict[str, list[dict[str, Any]]] | None = None,
3961 session_ids: dict[str, list[str]] | None = None,
3962 pr_ids: dict[str, list[str]] | None = None,
3963 release_tags: dict[str, list[str]] | None = None,
3964 ) -> None:
3965 """Print all seeded MuseHub URLs to stdout for manual browser verification."""
3966 BASE = "http://localhost:10001"
3967 print()
3968 print("=" * 72)
3969 print("🎵 MUSEHUB — ALL URLs (localhost:10001)")
3970 print("=" * 72)
3971
3972 print("\n── User profiles ────────────────────────────────────────────────")
3973 for _, uname, _ in list(USERS) + list(COMPOSER_USERS):
3974 print(f" {BASE}/users/{uname}")
3975 print(f" {BASE}/{uname} (redirects → above)")
3976
3977 print("\n── Explore / discover ───────────────────────────────────────────")
3978 print(f" {BASE}/explore")
3979 print(f" {BASE}/trending")
3980 print(f" {BASE}/search")
3981 print(f" {BASE}/feed")
3982
3983 all_repos_for_urls = list(REPOS[:8]) + list(GENRE_REPOS) # skip private fork repos
3984 for r in all_repos_for_urls: # Skip fork repos from URL dump
3985 owner, slug = r["owner"], r["slug"]
3986 repo_id = r["repo_id"]
3987 rbase = f"{BASE}/{owner}/{slug}"
3988
3989 print(f"\n── {owner}/{slug} ─────────────────────────────────────")
3990 print(f" Repo: {rbase}")
3991 print(f" Graph: {rbase}/graph")
3992 print(f" Timeline: {rbase}/timeline")
3993 print(f" Insights: {rbase}/insights")
3994 print(f" Credits: {rbase}/credits")
3995 print(f" Search: {rbase}/search")
3996
3997 if all_commits and repo_id in all_commits:
3998 commits = all_commits[repo_id]
3999 for c in commits[-3:]:
4000 print(f" Commit: {rbase}/commits/{c['commit_id'][:12]}")
4001 if commits:
4002 print(f" Diff: {rbase}/commits/{commits[-1]['commit_id'][:12]}/diff")
4003
4004 print(f" Issues: {rbase}/issues")
4005 print(f" PRs: {rbase}/pulls")
4006 print(f" Releases: {rbase}/releases")
4007 if release_tags and repo_id in release_tags:
4008 for tag in release_tags[repo_id]:
4009 print(f" {rbase}/releases/{tag}")
4010 print(f" Sessions: {rbase}/sessions")
4011 if session_ids and repo_id in session_ids:
4012 for sid in session_ids[repo_id][:2]:
4013 print(f" {rbase}/sessions/{sid}")
4014 print(f" Divergence:{rbase}/divergence")
4015 print(f" Context: {rbase}/context/main")
4016 print(f" Analysis: {rbase}/analysis/main/contour")
4017 print(f" {rbase}/analysis/main/tempo")
4018 print(f" {rbase}/analysis/main/dynamics")
4019
4020 print()
4021 print("=" * 72)
4022 print("✅ Seed complete.")
4023 print("=" * 72)
4024
4025
4026 async def main() -> None:
4027 """CLI entry point. Pass --force to wipe existing seed data before re-seeding."""
4028 force = "--force" in sys.argv
4029 db_url: str = settings.database_url or ""
4030 engine = create_async_engine(db_url, echo=False)
4031 async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) # type: ignore[call-overload] # SQLAlchemy typing: sessionmaker + class_=AsyncSession overload not reflected in stubs
4032 async with async_session() as db:
4033 await seed(db, force=force)
4034 await engine.dispose()
4035
4036
4037 if __name__ == "__main__":
4038 asyncio.run(main())