gabriel / musehub public
seed_musehub.py python
4007 lines 231.6 KB
88fcceed feat(seed): overhaul seed data with public-domain composers and cross-c… Gabriel Cardona <gabriel@tellurstori.com> 6d 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 all_repos = list(REPOS) + list(GENRE_REPOS)
2520 for r in all_repos:
2521 db.add(MusehubRepo(
2522 repo_id=r["repo_id"],
2523 name=r["name"],
2524 owner=r["owner"],
2525 slug=r["slug"],
2526 owner_user_id=r["owner_user_id"],
2527 visibility=r["visibility"],
2528 description=r["description"],
2529 tags=r["tags"],
2530 key_signature=r["key_signature"],
2531 tempo_bpm=r["tempo_bpm"],
2532 created_at=_now(days=r["days_ago"]),
2533 ))
2534 print(f" ✅ Repos: {len(all_repos)} ({len(REPOS)} original community + {len(GENRE_REPOS)} genre/archive/remix)")
2535
2536 await db.flush()
2537
2538 # ── 3. Commits + Branches ─────────────────────────────────────
2539 all_commits: dict[str, list[dict[str, Any]]] = {}
2540 total_commits = 0
2541 for r in all_repos:
2542 repo_id = r["repo_id"]
2543 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2544 n = COMMIT_COUNTS.get(repo_id, 20)
2545 commits = _make_commits(repo_id, rkey, n)
2546 all_commits[repo_id] = commits
2547 total_commits += len(commits)
2548 for c in commits:
2549 db.add(MusehubCommit(**c))
2550 # main branch always points to HEAD
2551 db.add(MusehubBranch(repo_id=repo_id, name="main",
2552 head_commit_id=commits[-1]["commit_id"]))
2553 if repo_id in GENRE_REPO_BRANCHES:
2554 # Genre archive repos: use specific named branches
2555 for branch_name, offset in GENRE_REPO_BRANCHES[repo_id]:
2556 idx = max(0, len(commits) - 1 - offset)
2557 db.add(MusehubBranch(
2558 repo_id=repo_id,
2559 name=branch_name,
2560 head_commit_id=commits[idx]["commit_id"],
2561 ))
2562 else:
2563 # Original repos: generic feature branches
2564 if len(commits) > 10:
2565 db.add(MusehubBranch(repo_id=repo_id, name="feat/develop",
2566 head_commit_id=commits[-4]["commit_id"]))
2567 if len(commits) > 20:
2568 db.add(MusehubBranch(repo_id=repo_id, name="experiment/alternate-harmony",
2569 head_commit_id=commits[-8]["commit_id"]))
2570 print(f" ✅ Commits: {total_commits} across {len(all_repos)} repos")
2571
2572 await db.flush()
2573
2574 # ── 4. Objects (track breakdown bar) ──────────────────────────
2575 obj_count = 0
2576 for r in all_repos:
2577 repo_id = r["repo_id"]
2578 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2579 tracks = REPO_TRACKS.get(rkey, REPO_TRACKS["neo-soul"])
2580 commits = all_commits.get(repo_id, [])
2581 if not commits:
2582 continue
2583 # Attach objects to the last 3 commits
2584 for commit in commits[-3:]:
2585 cid = commit["commit_id"]
2586 for role, path in tracks:
2587 obj_id = f"sha256:{_sha(f'{cid}-{path}')}"
2588 db.add(MusehubObject(
2589 object_id=obj_id,
2590 repo_id=repo_id,
2591 path=path,
2592 size_bytes=len(path) * 1024,
2593 disk_path=f"/app/objects/{repo_id}/{obj_id[7:15]}.mid",
2594 created_at=commit["timestamp"],
2595 ))
2596 obj_count += 1
2597 print(f" ✅ Objects: {obj_count} track files")
2598
2599 await db.flush()
2600
2601 # ── 4b. Labels (scoped to each repo — seeded before issues/PRs) ───────────
2602 _LABEL_DEFS: list[tuple[str, str, str]] = [
2603 # (name, color, description)
2604 ("bug", "#d73a4a", "Something isn't working correctly"),
2605 ("enhancement", "#a2eeef", "New feature or improvement request"),
2606 ("documentation","#0075ca", "Documentation update or correction"),
2607 ("question", "#d876e3", "Further information requested"),
2608 ("wontfix", "#ffffff", "This will not be addressed"),
2609 ("good first issue", "#7057ff", "Good for newcomers to the project"),
2610 ("help wanted", "#008672", "Extra attention needed"),
2611 ("in progress", "#e4e669", "Currently being worked on"),
2612 ("blocked", "#e11d48", "Blocked by another issue or dependency"),
2613 ("harmony", "#fbbf24", "Harmonic or tonal issue"),
2614 ("timing", "#6366f1", "Timing, groove or quantization issue"),
2615 ("mixing", "#10b981", "Mix balance, levels or EQ"),
2616 ("arrangement", "#f97316", "Arrangement or structure feedback"),
2617 ]
2618 # Structure: repo_id → {label_name → label_id}
2619 label_id_map: dict[str, dict[str, str]] = {}
2620 label_count = 0
2621 for r in all_repos:
2622 repo_id = r["repo_id"]
2623 label_id_map[repo_id] = {}
2624 for lname, lcolor, ldesc in _LABEL_DEFS:
2625 lid = _uid(f"label-{repo_id}-{lname}")
2626 db.add(MusehubLabel(
2627 id=lid,
2628 repo_id=repo_id,
2629 name=lname,
2630 color=lcolor,
2631 description=ldesc,
2632 created_at=_now(days=r["days_ago"]),
2633 ))
2634 label_id_map[repo_id][lname] = lid
2635 label_count += 1
2636 print(f" ✅ Labels: {label_count} ({len(_LABEL_DEFS)} per repo × {len(all_repos)} repos)")
2637
2638 await db.flush()
2639
2640 # ── 5a. Milestones (seed before issues so milestone_id can be referenced) ──
2641 # Structure: repo_id → {milestone_n → milestone_id}
2642 milestone_id_map: dict[str, dict[int, str]] = {}
2643 milestone_count = 0
2644 for r in REPOS:
2645 repo_id = r["repo_id"]
2646 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2647 ms_list = MILESTONE_TEMPLATES.get(rkey, [])
2648 if not ms_list:
2649 continue
2650 milestone_id_map[repo_id] = {}
2651 for ms in ms_list:
2652 mid = _uid(f"milestone-{repo_id}-{ms['n']}")
2653 due = _now(days=-ms["due_days"]) if ms.get("due_days") else None
2654 db.add(MusehubMilestone(
2655 milestone_id=mid,
2656 repo_id=repo_id,
2657 number=ms["n"],
2658 title=ms["title"],
2659 description=ms["description"],
2660 state=ms["state"],
2661 author=r["owner"],
2662 due_on=due,
2663 created_at=_now(days=r["days_ago"]),
2664 ))
2665 milestone_id_map[repo_id][ms["n"]] = mid
2666 milestone_count += 1
2667 print(f" ✅ Milestones: {milestone_count}")
2668
2669 await db.flush()
2670
2671 # ── 5b. Issues (track IDs for comment and milestone-link seeding) ──────────
2672 issue_count = 0
2673 # Structure: repo_id → {issue_n → issue_id}
2674 issue_id_map: dict[str, dict[int, str]] = {}
2675 for r in all_repos:
2676 repo_id = r["repo_id"]
2677 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2678 issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES)
2679 days_base = r["days_ago"]
2680 # Determine milestone assignment map for this repo: issue_n → milestone_n
2681 ms_assignments = MILESTONE_ISSUE_ASSIGNMENTS.get(rkey, {})
2682 issue_to_ms: dict[int, int] = {}
2683 for ms_number, issue_ns in ms_assignments.items():
2684 for iss_n in issue_ns:
2685 issue_to_ms[iss_n] = ms_number
2686
2687 issue_id_map[repo_id] = {}
2688 for iss in issue_list:
2689 iid = _uid(f"issue-{repo_id}-{iss['n']}")
2690 assigned_ms_n: int | None = issue_to_ms.get(iss["n"])
2691 ms_id: str | None = milestone_id_map.get(repo_id, {}).get(assigned_ms_n) if assigned_ms_n else None
2692 db.add(MusehubIssue(
2693 issue_id=iid,
2694 repo_id=repo_id,
2695 number=iss["n"],
2696 state=iss["state"],
2697 title=iss["title"],
2698 body=iss["body"],
2699 labels=iss["labels"],
2700 author=r["owner"],
2701 milestone_id=ms_id,
2702 created_at=_now(days=days_base - iss["n"] * 2),
2703 ))
2704 issue_id_map[repo_id][iss["n"]] = iid
2705 # Also populate MusehubIssueMilestone join table for repos with milestones
2706 if ms_id:
2707 db.add(MusehubIssueMilestone(issue_id=iid, milestone_id=ms_id))
2708 issue_count += 1
2709 print(f" ✅ Issues: {issue_count}")
2710
2711 await db.flush()
2712
2713 # ── 5b-ii. Issue labels (many-to-many join) ────────────────────────────────
2714 _ISSUE_LABEL_PICKS: list[list[str]] = [
2715 # Cycling pattern of label combos assigned to issues by index
2716 ["bug"],
2717 ["enhancement"],
2718 ["bug", "in progress"],
2719 ["question"],
2720 ["harmony"],
2721 ["timing"],
2722 ["mixing"],
2723 ["arrangement"],
2724 ["enhancement", "help wanted"],
2725 ["bug", "blocked"],
2726 ["documentation"],
2727 ["good first issue"],
2728 ["wontfix"],
2729 ["enhancement", "in progress"],
2730 ["harmony", "help wanted"],
2731 ]
2732 issue_label_count = 0
2733 for r in all_repos:
2734 repo_id = r["repo_id"]
2735 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2736 issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES)
2737 repo_labels = label_id_map.get(repo_id, {})
2738 for iss in issue_list:
2739 il_iid: str | None = issue_id_map.get(repo_id, {}).get(iss["n"])
2740 if not il_iid:
2741 continue
2742 picks = _ISSUE_LABEL_PICKS[iss["n"] % len(_ISSUE_LABEL_PICKS)]
2743 for lname in picks:
2744 il_lid: str | None = repo_labels.get(lname)
2745 if il_lid:
2746 db.add(MusehubIssueLabel(issue_id=il_iid, label_id=il_lid))
2747 issue_label_count += 1
2748 print(f" ✅ Issue labels: {issue_label_count}")
2749
2750 await db.flush()
2751
2752 # ── 5c. Issue comments (5-10 per issue, with @mentions and code blocks) ────
2753 issue_comment_count = 0
2754 users_list = [u[1] for u in USERS]
2755 for r in REPOS[:10]: # Comments on all non-fork repos
2756 repo_id = r["repo_id"]
2757 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
2758 issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES)
2759 for iss in issue_list:
2760 iss_iid = issue_id_map.get(repo_id, {}).get(iss["n"])
2761 if not iss_iid:
2762 continue
2763 # 5-10 comments per issue (varies by issue number parity)
2764 n_comments = 5 + (iss["n"] % 6)
2765 iss_cmt_parent: str | None = None
2766 for j in range(n_comments):
2767 cmt_seed = hash(repo_id + str(iss["n"]) + str(j)) % 1000
2768 body = _make_issue_comment_body(cmt_seed)
2769 musical_refs = _make_issue_musical_refs(body)
2770 author_idx = (abs(hash(repo_id)) + iss["n"] + j) % len(users_list)
2771 author = users_list[author_idx]
2772 cid = _uid(f"iss-comment-{repo_id}-{iss['n']}-{j}")
2773 db.add(MusehubIssueComment(
2774 comment_id=cid,
2775 issue_id=iss_iid,
2776 repo_id=repo_id,
2777 author=author,
2778 body=body,
2779 parent_id=iss_cmt_parent if j > 0 and j % 3 == 0 else None,
2780 musical_refs=musical_refs,
2781 created_at=_now(days=r["days_ago"] - iss["n"] * 2, hours=j * 2),
2782 ))
2783 issue_comment_count += 1
2784 # First comment becomes parent for threaded replies
2785 if j == 0:
2786 iss_cmt_parent = cid
2787 print(f" ✅ Issue comments: {issue_comment_count}")
2788
2789 await db.flush()
2790
2791 # ── 6. Pull Requests ──────────────────────────────────────────
2792 pr_count = 0
2793 pr_ids: dict[str, list[str]] = {}
2794 for r in all_repos:
2795 repo_id = r["repo_id"]
2796 commits = all_commits.get(repo_id, [])
2797 prs = _make_prs(repo_id, commits, r["owner"])
2798 pr_ids[repo_id] = [p["pr_id"] for p in prs]
2799 for pr in prs:
2800 db.add(MusehubPullRequest(**pr))
2801 pr_count += 1
2802 print(f" ✅ Pull Requests: {pr_count}")
2803
2804 await db.flush()
2805
2806 # ── 6b. PR comments (3-8 per PR with target_type variety) ─────────────────
2807 PR_COMMENT_BODIES: list[tuple[str, str, str | None, float | None, float | None, int | None]] = [
2808 # (body, target_type, target_track, beat_start, beat_end, note_pitch)
2809 ("General: this PR looks good overall. The groove change is an improvement.",
2810 "general", None, None, None, None),
2811 ("The `bass` track changes in this PR need review — the wah envelope is still too slow.",
2812 "track", "bass", None, None, None),
2813 ("This region (beats 16-24) sounds much better with the humanized timing.",
2814 "region", "keys", 16.0, 24.0, None),
2815 ("The C#4 (MIDI 61) note in the Rhodes feels misplaced — should be D4 for the chord.",
2816 "note", "keys", None, None, 61),
2817 ("Great improvement in the horns section. The harmony is now correct.",
2818 "track", "horns", None, None, None),
2819 ("The beat 1-8 region on the bass now locks properly with the kick.",
2820 "region", "bass", 1.0, 8.0, None),
2821 ("The G3 (MIDI 55) in bar 7 creates an unwanted clash. Remove or lower octave.",
2822 "note", "strings", None, None, 55),
2823 ("Overall this PR solves the main issue. LGTM with minor nits.",
2824 "general", None, None, None, None),
2825 ("The `drums` ghost notes are much improved — much more human now.",
2826 "track", "drums", None, None, None),
2827 ("Beats 32-40 on the guitar feel slightly rushed. Did you check the quantize grid?",
2828 "region", "guitar", 32.0, 40.0, None),
2829 ]
2830
2831 pr_comment_count = 0
2832 for r in REPOS[:10]:
2833 repo_id = r["repo_id"]
2834 for pr_id_str in pr_ids.get(repo_id, []):
2835 # 3-8 comments per PR
2836 n_pr_comments = 3 + (abs(hash(pr_id_str)) % 6)
2837 parent_pr_cid: str | None = None
2838 for k in range(n_pr_comments):
2839 tmpl_idx = (abs(hash(pr_id_str)) + k) % len(PR_COMMENT_BODIES)
2840 body, ttype, ttrack, tbs, tbe, tnp = PR_COMMENT_BODIES[tmpl_idx]
2841 author_idx = (abs(hash(repo_id)) + k + 1) % len(users_list)
2842 pr_cid = _uid(f"pr-comment-{pr_id_str}-{k}")
2843 db.add(MusehubPRComment(
2844 comment_id=pr_cid,
2845 pr_id=pr_id_str,
2846 repo_id=repo_id,
2847 author=users_list[author_idx],
2848 body=body,
2849 target_type=ttype,
2850 target_track=ttrack,
2851 target_beat_start=tbs,
2852 target_beat_end=tbe,
2853 target_note_pitch=tnp,
2854 parent_comment_id=parent_pr_cid if k > 0 and k % 4 == 0 else None,
2855 created_at=_now(days=7 - k),
2856 ))
2857 pr_comment_count += 1
2858 if k == 0:
2859 parent_pr_cid = pr_cid
2860 print(f" ✅ PR comments: {pr_comment_count}")
2861
2862 await db.flush()
2863
2864 # ── 6c. PR Reviews (reviewer assignment + approved/changes_requested) ──────
2865 _PR_REVIEW_STATES = ["approved", "approved", "changes_requested", "pending", "dismissed"]
2866 _PR_REVIEW_BODIES = [
2867 "LGTM — the harmonic changes are solid and the voice-leading is now clean.",
2868 "Approved. The groove is much tighter after the timing adjustments.",
2869 "Changes requested: the bass still feels muddy in bars 9-16. Please reduce low-mids.",
2870 "Pending review — I'll listen through the changes this weekend.",
2871 "Approved with nits: the coda could be shorter, but the core change is correct.",
2872 "Changes requested: parallel fifths still present in bar 7 on the strings voice.",
2873 "LGTM — the new arrangement section is exactly what the composition needed.",
2874 ]
2875 pr_review_count = 0
2876 for r in REPOS[:10]:
2877 repo_id = r["repo_id"]
2878 owner = r["owner"]
2879 for pr_id_str in pr_ids.get(repo_id, []):
2880 # 1-2 reviewers per PR, drawn from the community pool (not the PR author)
2881 n_reviewers = 1 + (abs(hash(pr_id_str)) % 2)
2882 reviewers_pool = [u for u in users_list if u != owner]
2883 for ri in range(n_reviewers):
2884 reviewer = reviewers_pool[(abs(hash(pr_id_str)) + ri) % len(reviewers_pool)]
2885 state = _PR_REVIEW_STATES[(abs(hash(pr_id_str)) + ri) % len(_PR_REVIEW_STATES)]
2886 body = _PR_REVIEW_BODIES[(abs(hash(pr_id_str)) + ri) % len(_PR_REVIEW_BODIES)]
2887 submitted = _now(days=5 - ri) if state != "pending" else None
2888 db.add(MusehubPRReview(
2889 id=_uid(f"pr-review-{pr_id_str}-{ri}"),
2890 pr_id=pr_id_str,
2891 reviewer_username=reviewer,
2892 state=state,
2893 body=body if state != "pending" else None,
2894 submitted_at=submitted,
2895 created_at=_now(days=7 - ri),
2896 ))
2897 pr_review_count += 1
2898 print(f" ✅ PR reviews: {pr_review_count}")
2899
2900 await db.flush()
2901
2902 # ── 6d. PR Labels (label tags on pull requests) ────────────────────────────
2903 _PR_LABEL_PICKS: list[list[str]] = [
2904 ["enhancement"],
2905 ["bug"],
2906 ["enhancement", "in progress"],
2907 ["wontfix"],
2908 ]
2909 pr_label_count = 0
2910 for r in all_repos:
2911 repo_id = r["repo_id"]
2912 repo_labels = label_id_map.get(repo_id, {})
2913 for pi, pr_id_str in enumerate(pr_ids.get(repo_id, [])):
2914 picks = _PR_LABEL_PICKS[pi % len(_PR_LABEL_PICKS)]
2915 for lname in picks:
2916 prl_lid: str | None = repo_labels.get(lname)
2917 if prl_lid:
2918 db.add(MusehubPRLabel(pr_id=pr_id_str, label_id=prl_lid))
2919 pr_label_count += 1
2920 print(f" ✅ PR labels: {pr_label_count}")
2921
2922 await db.flush()
2923
2924 # ── 7. Releases ───────────────────────────────────────────────
2925 release_count = 0
2926 release_tags: dict[str, list[str]] = {}
2927 for r in all_repos:
2928 repo_id = r["repo_id"]
2929 commits = all_commits.get(repo_id, [])
2930 releases = _make_releases(repo_id, commits, r["name"], r["owner"])
2931 release_tags[repo_id] = [rel["tag"] for rel in releases]
2932 for rel in releases:
2933 db.add(MusehubRelease(**rel))
2934 release_count += 1
2935 print(f" ✅ Releases: {release_count}")
2936
2937 await db.flush()
2938
2939 # ── 7b. Release Assets (downloadable file attachments per release) ─────────
2940 _ASSET_TYPES: list[tuple[str, str, str, int]] = [
2941 # (name_suffix, label, content_type, approx_size_bytes)
2942 ("-midi-bundle.zip", "MIDI Bundle", "application/zip", 2_400_000),
2943 ("-stereo-mix.mp3", "Stereo Mix", "audio/mpeg", 8_200_000),
2944 ("-stems.zip", "Stems Archive", "application/zip", 42_000_000),
2945 ("-score.pdf", "Score PDF", "application/pdf", 1_100_000),
2946 ("-metadata.json", "Metadata", "application/json", 18_000),
2947 ]
2948 # Only full releases (v1.0.0) get all 5 assets; earlier releases get 2.
2949 release_asset_count = 0
2950 for r in all_repos:
2951 repo_id = r["repo_id"]
2952 tags = release_tags.get(repo_id, [])
2953 for ti, tag in enumerate(tags):
2954 # release_id is deterministic — matches what _make_releases sets
2955 rel_id = _uid(f"release-{repo_id}-{tag}")
2956 # Full release gets all asset types; earlier releases get 2
2957 n_assets = len(_ASSET_TYPES) if ti == len(tags) - 1 else 2
2958 base_slug = r["slug"]
2959 for ai, (sfx, label, ctype, base_size) in enumerate(_ASSET_TYPES[:n_assets]):
2960 dl_count = max(0, (50 - ti * 15) - ai * 5 + abs(hash(repo_id + tag)) % 20)
2961 db.add(MusehubReleaseAsset(
2962 asset_id=_uid(f"asset-{rel_id}-{ai}"),
2963 release_id=rel_id,
2964 repo_id=repo_id,
2965 name=f"{base_slug}-{tag}{sfx}",
2966 label=label,
2967 content_type=ctype,
2968 size=base_size + abs(hash(repo_id)) % 500_000,
2969 download_url=f"/releases/{repo_id}/{tag}{sfx}",
2970 download_count=dl_count,
2971 created_at=_now(days=max(1, 45 - ti * 20)),
2972 ))
2973 release_asset_count += 1
2974 print(f" ✅ Release assets: {release_asset_count}")
2975
2976 await db.flush()
2977
2978 # ── 8. Sessions ───────────────────────────────────────────────
2979 session_count = 0
2980 session_ids: dict[str, list[str]] = {}
2981 for r in all_repos:
2982 repo_id = r["repo_id"]
2983 commits = all_commits.get(repo_id, [])
2984 sessions = _make_sessions(repo_id, r["owner"], commits)
2985 session_ids[repo_id] = [s["session_id"] for s in sessions]
2986 for sess in sessions:
2987 db.add(MusehubSession(**sess))
2988 session_count += 1
2989 print(f" ✅ Sessions: {session_count}")
2990
2991 await db.flush()
2992
2993 # ── 8b. Collaborators (repo access beyond owner) ───────────────────────────
2994 # Each active repo gets 1-3 collaborators from the community pool.
2995 # Collaborators have write permission; the most active repos get an admin.
2996 _COLLAB_CONFIGS: list[tuple[str, str, list[tuple[str, str]]]] = [
2997 # (repo_id, owner_user_id, [(collab_user_id, permission)])
2998 (REPO_NEO_SOUL, GABRIEL, [(MARCUS, "write"), (SOFIA, "write"), (AALIYA, "admin")]),
2999 (REPO_MODAL_JAZZ, GABRIEL, [(MARCUS, "write"), (CHEN, "write")]),
3000 (REPO_AMBIENT, SOFIA, [(YUKI, "write"), (PIERRE, "write"), (GABRIEL, "admin")]),
3001 (REPO_AFROBEAT, AALIYA, [(FATOU, "write"), (GABRIEL, "write")]),
3002 (REPO_MICROTONAL, CHEN, [(PIERRE, "write")]),
3003 (REPO_DRUM_MACHINE, FATOU, [(AALIYA, "write"), (MARCUS, "write")]),
3004 (REPO_CHANSON, PIERRE, [(SOFIA, "write")]),
3005 (REPO_GRANULAR, YUKI, [(SOFIA, "write"), (CHEN, "write")]),
3006 (REPO_FUNK_SUITE, MARCUS, [(GABRIEL, "write"), (AALIYA, "admin")]),
3007 (REPO_JAZZ_TRIO, MARCUS, [(GABRIEL, "write")]),
3008 (REPO_NEO_BAROQUE, GABRIEL, [(PIERRE, "write"), (CHEN, "write")]),
3009 (REPO_JAZZ_CHOPIN, AALIYA, [(GABRIEL, "write"), (MARCUS, "write")]),
3010 (REPO_COMMUNITY, GABRIEL, [(SOFIA, "admin"), (MARCUS, "write"), (YUKI, "write"),
3011 (AALIYA, "write"), (CHEN, "write"), (FATOU, "write"), (PIERRE, "write")]),
3012 ]
3013 collab_count = 0
3014 for repo_id, owner_uid, collab_list in _COLLAB_CONFIGS:
3015 for collab_uid, perm in collab_list:
3016 accepted = _now(days=abs(hash(collab_uid + repo_id)) % 20 + 1)
3017 db.add(MusehubCollaborator(
3018 id=_uid(f"collab-{repo_id}-{collab_uid}"),
3019 repo_id=repo_id,
3020 user_id=collab_uid,
3021 permission=perm,
3022 invited_by=owner_uid,
3023 invited_at=_now(days=abs(hash(collab_uid + repo_id)) % 20 + 3),
3024 accepted_at=accepted,
3025 ))
3026 collab_count += 1
3027 print(f" ✅ Collaborators: {collab_count}")
3028
3029 await db.flush()
3030
3031 # ── 8c. Stash (shelved in-progress work per user/repo) ────────────────────
3032 _STASH_MESSAGES: list[str] = [
3033 "WIP: Rhodes chord voicings — not ready to commit",
3034 "Experiment: tritone sub on IV chord — might revert",
3035 "Sketching counter-melody — needs more work",
3036 "Bass line draft — 3-against-4 groove not locked yet",
3037 "Drum fills experiment — comparing two approaches",
3038 "Harmony sketch: parallel 10ths — maybe too classical?",
3039 "Tempo map idea: ritardando at bar 36 — not sure yet",
3040 ]
3041 _STASH_CONFIGS: list[tuple[str, str, str]] = [
3042 # (repo_id, user_id, branch)
3043 (REPO_NEO_SOUL, GABRIEL, "feat/counter-melody"),
3044 (REPO_AMBIENT, SOFIA, "experiment/drone-layer"),
3045 (REPO_AFROBEAT, AALIYA, "feat/brass-section"),
3046 (REPO_FUNK_SUITE, MARCUS, "experiment/fretless-bass"),
3047 (REPO_MICROTONAL, CHEN, "feat/spectral-harmony"),
3048 (REPO_GRANULAR, YUKI, "experiment/formant-filter"),
3049 (REPO_CHANSON, PIERRE, "feat/coda-extension"),
3050 (REPO_COMMUNITY, GABRIEL, "collab/all-genre-finale"),
3051 (REPO_MODAL_JAZZ, GABRIEL, "feat/mcoy-voicings"),
3052 (REPO_NEO_BAROQUE, GABRIEL, "experiment/fugue-development"),
3053 ]
3054 stash_count = 0
3055 stash_entry_count = 0
3056 for si, (repo_id, user_id, branch) in enumerate(_STASH_CONFIGS):
3057 stash_id = _uid(f"stash-{repo_id}-{user_id}-{si}")
3058 msg = _STASH_MESSAGES[si % len(_STASH_MESSAGES)]
3059 is_applied = si % 5 == 0 # Every 5th stash has been popped
3060 applied_at = _now(days=1) if is_applied else None
3061 db.add(MusehubStash(
3062 id=stash_id,
3063 repo_id=repo_id,
3064 user_id=user_id,
3065 branch=branch,
3066 message=msg,
3067 is_applied=is_applied,
3068 created_at=_now(days=si + 2),
3069 applied_at=applied_at,
3070 ))
3071 stash_count += 1
3072 # 2-4 MIDI file entries per stash
3073 rkey = REPO_KEY_MAP.get(repo_id, "neo-soul")
3074 stash_tracks = REPO_TRACKS.get(rkey, REPO_TRACKS["neo-soul"])
3075 n_entries = 2 + (si % 3)
3076 for ei, (role, fpath) in enumerate(stash_tracks[:n_entries]):
3077 obj_id = f"sha256:{_sha(f'stash-obj-{stash_id}-{role}')}"
3078 db.add(MusehubStashEntry(
3079 id=_uid(f"stash-entry-{stash_id}-{ei}"),
3080 stash_id=stash_id,
3081 path=f"tracks/{fpath}",
3082 object_id=obj_id,
3083 position=ei,
3084 ))
3085 stash_entry_count += 1
3086 print(f" ✅ Stash: {stash_count} stashes, {stash_entry_count} entries")
3087
3088 await db.flush()
3089
3090 # ── 9. Stars (cross-repo) ─────────────────────────────────────
3091 # Every community user stars 5-10 repos; genre/composer archives also get
3092 # starred by community users who inspired or forked from them. 50+ total.
3093 star_pairs = [
3094 # Community repos — broad community engagement
3095 (SOFIA, REPO_NEO_SOUL, 20), (MARCUS, REPO_NEO_SOUL, 18),
3096 (YUKI, REPO_NEO_SOUL, 15), (AALIYA, REPO_NEO_SOUL, 12),
3097 (CHEN, REPO_NEO_SOUL, 10), (FATOU, REPO_NEO_SOUL, 8),
3098 (PIERRE, REPO_NEO_SOUL, 6),
3099
3100 (GABRIEL, REPO_AMBIENT, 14), (MARCUS, REPO_AMBIENT, 12),
3101 (YUKI, REPO_AMBIENT, 10), (AALIYA, REPO_AMBIENT, 9),
3102 (CHEN, REPO_AMBIENT, 7), (FATOU, REPO_AMBIENT, 5),
3103 (PIERRE, REPO_AMBIENT, 3),
3104
3105 (GABRIEL, REPO_AFROBEAT, 8), (SOFIA, REPO_AFROBEAT, 7),
3106 (MARCUS, REPO_AFROBEAT, 6), (YUKI, REPO_AFROBEAT, 5),
3107 (CHEN, REPO_AFROBEAT, 4), (PIERRE, REPO_AFROBEAT, 3),
3108
3109 (GABRIEL, REPO_FUNK_SUITE, 12), (SOFIA, REPO_FUNK_SUITE, 10),
3110 (AALIYA, REPO_FUNK_SUITE, 9), (CHEN, REPO_FUNK_SUITE, 7),
3111 (FATOU, REPO_FUNK_SUITE, 5),
3112
3113 (GABRIEL, REPO_MODAL_JAZZ, 11), (SOFIA, REPO_MODAL_JAZZ, 8),
3114 (MARCUS, REPO_MODAL_JAZZ, 6), (AALIYA, REPO_MODAL_JAZZ, 4),
3115
3116 (GABRIEL, REPO_JAZZ_TRIO, 9), (SOFIA, REPO_JAZZ_TRIO, 7),
3117 (AALIYA, REPO_JAZZ_TRIO, 5), (CHEN, REPO_JAZZ_TRIO, 3),
3118
3119 (GABRIEL, REPO_DRUM_MACHINE, 6), (MARCUS, REPO_DRUM_MACHINE, 4),
3120 (GABRIEL, REPO_GRANULAR, 5), (MARCUS, REPO_GRANULAR, 3),
3121 (GABRIEL, REPO_CHANSON, 4), (SOFIA, REPO_CHANSON, 3),
3122 (GABRIEL, REPO_MICROTONAL, 3), (SOFIA, REPO_MICROTONAL, 2),
3123
3124 # Genre/composer archive repos starred by community users who draw
3125 # inspiration from or fork into them
3126 (GABRIEL, REPO_GOLDBERG, 25), (SOFIA, REPO_GOLDBERG, 22),
3127 (MARCUS, REPO_GOLDBERG, 18), (AALIYA, REPO_GOLDBERG, 15),
3128 (CHEN, REPO_GOLDBERG, 12), (FATOU, REPO_GOLDBERG, 9),
3129 (PIERRE, REPO_GOLDBERG, 7), (YUKI, REPO_GOLDBERG, 5),
3130
3131 (GABRIEL, REPO_WTC, 24), (SOFIA, REPO_WTC, 20),
3132 (CHEN, REPO_WTC, 16), (MARCUS, REPO_WTC, 13),
3133 (YUKI, REPO_WTC, 10), (PIERRE, REPO_WTC, 6),
3134
3135 (GABRIEL, REPO_NOCTURNES, 22), (AALIYA, REPO_NOCTURNES, 18),
3136 (SOFIA, REPO_NOCTURNES, 15), (CHEN, REPO_NOCTURNES, 11),
3137 (MARCUS, REPO_NOCTURNES, 8), (YUKI, REPO_NOCTURNES, 5),
3138
3139 (MARCUS, REPO_MAPLE_LEAF, 20), (GABRIEL, REPO_MAPLE_LEAF, 17),
3140 (AALIYA, REPO_MAPLE_LEAF, 13), (FATOU, REPO_MAPLE_LEAF, 9),
3141 (PIERRE, REPO_MAPLE_LEAF, 6),
3142
3143 (CHEN, REPO_CIN_STRINGS, 19), (GABRIEL, REPO_CIN_STRINGS, 15),
3144 (SOFIA, REPO_CIN_STRINGS, 12), (YUKI, REPO_CIN_STRINGS, 8),
3145 (MARCUS, REPO_CIN_STRINGS, 5),
3146
3147 # Community genre-fusion repos (created as forks of composer archives)
3148 (SOFIA, REPO_NEO_BAROQUE, 14), (MARCUS, REPO_NEO_BAROQUE, 11),
3149 (CHEN, REPO_NEO_BAROQUE, 8), (YUKI, REPO_NEO_BAROQUE, 5),
3150 (PIERRE, REPO_NEO_BAROQUE, 3),
3151
3152 (GABRIEL, REPO_JAZZ_CHOPIN, 12), (MARCUS, REPO_JAZZ_CHOPIN, 9),
3153 (SOFIA, REPO_JAZZ_CHOPIN, 7), (CHEN, REPO_JAZZ_CHOPIN, 4),
3154
3155 (GABRIEL, REPO_RAGTIME_EDM, 10), (AALIYA, REPO_RAGTIME_EDM, 8),
3156 (FATOU, REPO_RAGTIME_EDM, 6), (YUKI, REPO_RAGTIME_EDM, 4),
3157
3158 (GABRIEL, REPO_FILM_SCORE, 9), (SOFIA, REPO_FILM_SCORE, 7),
3159 (MARCUS, REPO_FILM_SCORE, 5), (AALIYA, REPO_FILM_SCORE, 3),
3160
3161 (GABRIEL, REPO_COMMUNITY, 8), (SOFIA, REPO_COMMUNITY, 6),
3162 (MARCUS, REPO_COMMUNITY, 5), (AALIYA, REPO_COMMUNITY, 4),
3163 (CHEN, REPO_COMMUNITY, 3), (FATOU, REPO_COMMUNITY, 2),
3164 (PIERRE, REPO_COMMUNITY, 1),
3165 ]
3166 for user_id, repo_id, days in star_pairs:
3167 db.add(MusehubStar(repo_id=repo_id, user_id=user_id,
3168 created_at=_now(days=days)))
3169 print(f" ✅ Stars: {len(star_pairs)}")
3170
3171 await db.flush()
3172
3173 # ── 10. Comments ─────────────────────────────────────────────
3174 COMMENT_BODIES = [
3175 "This groove is incredible — the 3-against-4 polyrhythm is exactly what this track needed.",
3176 "Love the Rhodes voicings here. Upper-structure triads give it that sophisticated neo-soul feel.",
3177 "The humanization really helped. Feels much more like a live performance now.",
3178 "I think the bridge needs more harmonic tension. The IV-I resolution is too settled.",
3179 "That trumpet counter-melody is stunning. It perfectly answers the Rhodes line.",
3180 "Could we push the bass a bit more? It's sitting a little behind the kick drum.",
3181 "The string pizzicato in the verse is a beautiful subtle touch.",
3182 "I'm not sure about the guitar scratch — feels a bit busy with the Rhodes.",
3183 "This is really coming together. The dynamic arc from intro to chorus is perfect.",
3184 "The call-and-response between horns and strings is very Quincy Jones.",
3185 ]
3186
3187 comment_count = 0
3188 for r in REPOS[:6]: # Comments on first 6 repos
3189 repo_id = r["repo_id"]
3190 commits = all_commits.get(repo_id, [])
3191 if not commits:
3192 continue
3193 # Comments on latest 3 commits
3194 for i, commit in enumerate(commits[-3:]):
3195 for j in range(3):
3196 body = COMMENT_BODIES[(i * 3 + j) % len(COMMENT_BODIES)]
3197 author_ids = [GABRIEL, SOFIA, MARCUS, YUKI, AALIYA, CHEN, FATOU, PIERRE]
3198 author = [u[1] for u in USERS if u[0] == author_ids[(i + j + hash(repo_id)) % len(author_ids)]][0]
3199 comment_id = _uid(f"comment-{repo_id}-{i}-{j}")
3200 db.add(MusehubComment(
3201 comment_id=comment_id,
3202 repo_id=repo_id,
3203 target_type="commit",
3204 target_id=commit["commit_id"],
3205 author=author,
3206 body=body,
3207 parent_id=None,
3208 created_at=_now(days=2, hours=i * 3 + j),
3209 ))
3210 comment_count += 1
3211 # Add a reply to first comment on each commit
3212 if j == 0:
3213 reply_author = [u[1] for u in USERS if u[1] != author][0]
3214 db.add(MusehubComment(
3215 comment_id=_uid(f"reply-{repo_id}-{i}-{j}"),
3216 repo_id=repo_id,
3217 target_type="commit",
3218 target_id=commit["commit_id"],
3219 author=reply_author,
3220 body="Totally agree — this part really elevates the whole track.",
3221 parent_id=comment_id,
3222 created_at=_now(days=1, hours=i * 2),
3223 ))
3224 comment_count += 1
3225 print(f" ✅ Comments: {comment_count}")
3226
3227 await db.flush()
3228
3229 # ── 11. Reactions ─────────────────────────────────────────────
3230 # All 8 emoji types from the spec: 👍❤️🎵🔥🎹👏🤔😢. 200+ total across
3231 # both community and genre-archive repos.
3232 EMOJIS = ["👍", "❤️", "🎵", "🔥", "🎹", "👏", "🤔", "😢"]
3233 reaction_count = 0
3234 all_community_users = [GABRIEL, SOFIA, MARCUS, YUKI, AALIYA, CHEN, FATOU, PIERRE]
3235 # Community repos — all 8 users react to last 5 commits on first 6 repos
3236 for r in REPOS[:6]:
3237 repo_id = r["repo_id"]
3238 commits = all_commits.get(repo_id, [])
3239 if not commits:
3240 continue
3241 for commit in commits[-5:]:
3242 for i, uid in enumerate(all_community_users):
3243 emoji = EMOJIS[i % len(EMOJIS)]
3244 try:
3245 db.add(MusehubReaction(
3246 reaction_id=_uid(f"reaction-{repo_id}-{commit['commit_id'][:8]}-{uid}"),
3247 repo_id=repo_id,
3248 target_type="commit",
3249 target_id=commit["commit_id"],
3250 user_id=uid,
3251 emoji=emoji,
3252 created_at=_now(days=1),
3253 ))
3254 reaction_count += 1
3255 except Exception:
3256 pass
3257 # Genre-archive repos — community users react to last 3 commits, rotating
3258 # through all emoji types to ensure full coverage
3259 genre_reaction_repos = [
3260 REPO_GOLDBERG, REPO_WTC, REPO_NOCTURNES, REPO_MAPLE_LEAF, REPO_CIN_STRINGS,
3261 ]
3262 for repo_id in genre_reaction_repos:
3263 commits = all_commits.get(repo_id, [])
3264 if not commits:
3265 continue
3266 for ci, commit in enumerate(commits[-3:]):
3267 for ui, uid in enumerate(all_community_users):
3268 emoji = EMOJIS[(ci * len(all_community_users) + ui) % len(EMOJIS)]
3269 try:
3270 db.add(MusehubReaction(
3271 reaction_id=_uid(f"reaction-g-{repo_id[:12]}-{commit['commit_id'][:8]}-{uid}"),
3272 repo_id=repo_id,
3273 target_type="commit",
3274 target_id=commit["commit_id"],
3275 user_id=uid,
3276 emoji=emoji,
3277 created_at=_now(days=2),
3278 ))
3279 reaction_count += 1
3280 except Exception:
3281 pass
3282 print(f" ✅ Reactions: {reaction_count}")
3283
3284 await db.flush()
3285
3286 # ── 12. Follows (social graph) ────────────────────────────────
3287 # Rich bidirectional follow graph: gabriel↔sofia, marcus↔fatou,
3288 # yuki↔chen, aaliya↔pierre, plus composer-to-community asymmetric follows.
3289 # 60+ total, preserving all prior pairs.
3290 follow_pairs = [
3291 # Original follows — gabriel is the hub (everyone follows him)
3292 (SOFIA, GABRIEL), (MARCUS, GABRIEL), (YUKI, GABRIEL),
3293 (AALIYA, GABRIEL), (CHEN, GABRIEL), (FATOU, GABRIEL),
3294 (PIERRE, GABRIEL),
3295
3296 # Bidirectional close collaborator pairs (symmetric)
3297 (GABRIEL, SOFIA), (SOFIA, GABRIEL), # already added above, deduped by _uid
3298 (MARCUS, FATOU), (FATOU, MARCUS),
3299 (YUKI, CHEN), (CHEN, YUKI),
3300 (AALIYA, PIERRE), (PIERRE, AALIYA),
3301
3302 # Cross-community follows
3303 (GABRIEL, MARCUS), (SOFIA, MARCUS), (AALIYA, MARCUS),
3304 (GABRIEL, YUKI), (SOFIA, YUKI),
3305 (GABRIEL, AALIYA), (MARCUS, AALIYA),
3306 (GABRIEL, CHEN),
3307 (GABRIEL, FATOU), (AALIYA, FATOU),
3308 (GABRIEL, PIERRE), (SOFIA, PIERRE),
3309 (MARCUS, SOFIA), (PIERRE, SOFIA),
3310 (MARCUS, YUKI), (FATOU, YUKI),
3311 (PIERRE, MARCUS), (YUKI, MARCUS),
3312 (CHEN, MARCUS), (FATOU, SOFIA),
3313 (YUKI, AALIYA), (CHEN, AALIYA), (FATOU, AALIYA),
3314 (MARCUS, CHEN), (AALIYA, CHEN), (PIERRE, CHEN),
3315 (SOFIA, FATOU), (CHEN, FATOU), (PIERRE, FATOU),
3316 (YUKI, PIERRE), (MARCUS, PIERRE), (CHEN, PIERRE),
3317 (SOFIA, CHEN), (AALIYA, YUKI), (FATOU, CHEN),
3318
3319 # Community users follow composer archive maintainers
3320 (GABRIEL, BACH), (SOFIA, BACH), (MARCUS, BACH),
3321 (CHEN, BACH), (AALIYA, BACH), (YUKI, BACH),
3322 (GABRIEL, CHOPIN), (AALIYA, CHOPIN), (SOFIA, CHOPIN),
3323 (MARCUS, SCOTT_JOPLIN), (GABRIEL, SCOTT_JOPLIN),
3324 (FATOU, SCOTT_JOPLIN),
3325 (CHEN, KEVIN_MACLEOD), (GABRIEL, KEVIN_MACLEOD),
3326 ]
3327 # Deduplicate pairs so _uid collisions never cause constraint errors
3328 seen_follows: set[tuple[str, str]] = set()
3329 deduped_follows = []
3330 for pair in follow_pairs:
3331 if pair not in seen_follows:
3332 seen_follows.add(pair)
3333 deduped_follows.append(pair)
3334 for follower, followee in deduped_follows:
3335 db.add(MusehubFollow(
3336 follow_id=_uid(f"follow-{follower}-{followee}"),
3337 follower_id=follower,
3338 followee_id=followee,
3339 created_at=_now(days=15),
3340 ))
3341 print(f" ✅ Follows: {len(deduped_follows)}")
3342
3343 await db.flush()
3344
3345 # ── 13. Watches ───────────────────────────────────────────────
3346 # 60+ total: community users watch each other's repos and the composer
3347 # archive repos they draw inspiration from.
3348 watch_pairs = [
3349 # Community repo watches
3350 (GABRIEL, REPO_AMBIENT), (GABRIEL, REPO_AFROBEAT), (GABRIEL, REPO_FUNK_SUITE),
3351 (GABRIEL, REPO_JAZZ_TRIO), (GABRIEL, REPO_GRANULAR), (GABRIEL, REPO_CHANSON),
3352 (GABRIEL, REPO_MICROTONAL), (GABRIEL, REPO_DRUM_MACHINE),
3353
3354 (SOFIA, REPO_NEO_SOUL), (SOFIA, REPO_FUNK_SUITE), (SOFIA, REPO_CHANSON),
3355 (SOFIA, REPO_MODAL_JAZZ), (SOFIA, REPO_AFROBEAT), (SOFIA, REPO_MICROTONAL),
3356
3357 (MARCUS, REPO_NEO_SOUL), (MARCUS, REPO_AMBIENT), (MARCUS, REPO_AFROBEAT),
3358 (MARCUS, REPO_MODAL_JAZZ), (MARCUS, REPO_JAZZ_TRIO), (MARCUS, REPO_FUNK_SUITE),
3359
3360 (YUKI, REPO_AMBIENT), (YUKI, REPO_GRANULAR), (YUKI, REPO_MICROTONAL),
3361 (YUKI, REPO_NEO_SOUL), (YUKI, REPO_DRUM_MACHINE),
3362
3363 (AALIYA, REPO_NEO_SOUL), (AALIYA, REPO_AFROBEAT), (AALIYA, REPO_FUNK_SUITE),
3364 (AALIYA, REPO_MODAL_JAZZ), (AALIYA, REPO_JAZZ_TRIO), (AALIYA, REPO_CHANSON),
3365
3366 (CHEN, REPO_MICROTONAL), (CHEN, REPO_AMBIENT), (CHEN, REPO_GRANULAR),
3367 (CHEN, REPO_MODAL_JAZZ), (CHEN, REPO_NEO_SOUL), (CHEN, REPO_DRUM_MACHINE),
3368
3369 (FATOU, REPO_AFROBEAT), (FATOU, REPO_DRUM_MACHINE),(FATOU, REPO_FUNK_SUITE),
3370 (FATOU, REPO_NEO_SOUL), (FATOU, REPO_MODAL_JAZZ), (FATOU, REPO_GRANULAR),
3371
3372 (PIERRE, REPO_CHANSON), (PIERRE, REPO_AMBIENT), (PIERRE, REPO_NEO_SOUL),
3373 (PIERRE, REPO_MODAL_JAZZ), (PIERRE, REPO_MICROTONAL),
3374
3375 # Composer archive repo watches — community users watching source material
3376 (GABRIEL, REPO_GOLDBERG), (GABRIEL, REPO_WTC), (GABRIEL, REPO_NOCTURNES),
3377 (GABRIEL, REPO_MAPLE_LEAF), (GABRIEL, REPO_CIN_STRINGS),
3378
3379 (SOFIA, REPO_GOLDBERG), (SOFIA, REPO_WTC), (SOFIA, REPO_NOCTURNES),
3380 (MARCUS, REPO_MAPLE_LEAF), (MARCUS, REPO_GOLDBERG), (MARCUS, REPO_WTC),
3381 (AALIYA, REPO_NOCTURNES), (AALIYA, REPO_MAPLE_LEAF),
3382 (CHEN, REPO_CIN_STRINGS), (CHEN, REPO_GOLDBERG),
3383 (YUKI, REPO_WTC), (FATOU, REPO_MAPLE_LEAF), (PIERRE, REPO_NOCTURNES),
3384
3385 # Genre-fusion community repos
3386 (SOFIA, REPO_NEO_BAROQUE), (MARCUS, REPO_NEO_BAROQUE),
3387 (GABRIEL, REPO_JAZZ_CHOPIN), (MARCUS, REPO_RAGTIME_EDM),
3388 (GABRIEL, REPO_FILM_SCORE), (SOFIA, REPO_FILM_SCORE),
3389 (GABRIEL, REPO_COMMUNITY), (AALIYA, REPO_COMMUNITY),
3390 ]
3391 for user_id, repo_id in watch_pairs:
3392 db.add(MusehubWatch(
3393 watch_id=_uid(f"watch-{user_id}-{repo_id}"),
3394 user_id=user_id,
3395 repo_id=repo_id,
3396 created_at=_now(days=10),
3397 ))
3398 print(f" ✅ Watches: {len(watch_pairs)}")
3399
3400 await db.flush()
3401
3402 # ── 14. Notifications ─────────────────────────────────────────
3403 # 15-25 unread per user, all event types, all 8 users. 15-25 unread means
3404 # we create 20 notifications per user with the first 5 marked as read and
3405 # the remaining 15 unread — satisfying the "15-25 unread" spec.
3406 EVENT_TYPES = [
3407 "comment", "pr_opened", "pr_merged", "issue_opened", "new_commit",
3408 "new_follower", "star", "fork", "release", "watch",
3409 ]
3410 NOTIFS_PER_USER = 20 # 5 read + 15 unread = 15 unread per user
3411 READ_THRESHOLD = 5 # first N are read
3412 all_repos_flat = [r["repo_id"] for r in (list(REPOS) + list(GENRE_REPOS))]
3413 notif_count = 0
3414 for i, (uid, uname, _) in enumerate(USERS):
3415 for j in range(NOTIFS_PER_USER):
3416 actor_user = USERS[(i + j + 1) % len(USERS)]
3417 db.add(MusehubNotification(
3418 notif_id=_uid(f"notif2-{uid}-{j}"),
3419 recipient_id=uid,
3420 event_type=EVENT_TYPES[j % len(EVENT_TYPES)],
3421 repo_id=all_repos_flat[(i + j) % len(all_repos_flat)],
3422 actor=actor_user[1],
3423 payload={"message": f"Notification {j + 1} for {uname}"},
3424 is_read=j < READ_THRESHOLD,
3425 created_at=_now(days=j),
3426 ))
3427 notif_count += 1
3428 print(f" ✅ Notifications: {notif_count} ({NOTIFS_PER_USER - READ_THRESHOLD} unread per user)")
3429
3430 await db.flush()
3431
3432 # ── 15. Forks ─────────────────────────────────────────────────
3433 # Original community-to-community forks
3434 db.add(MusehubFork(
3435 fork_id=_uid("fork-neo-soul-marcus"),
3436 source_repo_id=REPO_NEO_SOUL,
3437 fork_repo_id=REPO_NEO_SOUL_FORK,
3438 forked_by="marcus",
3439 created_at=_now(days=10),
3440 ))
3441 db.add(MusehubFork(
3442 fork_id=_uid("fork-ambient-yuki"),
3443 source_repo_id=REPO_AMBIENT,
3444 fork_repo_id=REPO_AMBIENT_FORK,
3445 forked_by="yuki",
3446 created_at=_now(days=5),
3447 ))
3448 # Genre-archive → community forks: each community repo that remixes a
3449 # composer's archive is modelled as a fork of the canonical archive repo.
3450 # marcus/ragtime-edm ← scott_joplin/maple-leaf-rag
3451 db.add(MusehubFork(
3452 fork_id=_uid("fork-maple-leaf-marcus"),
3453 source_repo_id=REPO_MAPLE_LEAF,
3454 fork_repo_id=REPO_RAGTIME_EDM,
3455 forked_by="marcus",
3456 created_at=_now(days=30),
3457 ))
3458 # aaliya/jazz-chopin ← chopin/nocturnes
3459 db.add(MusehubFork(
3460 fork_id=_uid("fork-nocturnes-aaliya"),
3461 source_repo_id=REPO_NOCTURNES,
3462 fork_repo_id=REPO_JAZZ_CHOPIN,
3463 forked_by="aaliya",
3464 created_at=_now(days=28),
3465 ))
3466 # gabriel/neo-baroque ← bach/goldberg-variations
3467 db.add(MusehubFork(
3468 fork_id=_uid("fork-goldberg-gabriel-neobaroque"),
3469 source_repo_id=REPO_GOLDBERG,
3470 fork_repo_id=REPO_NEO_BAROQUE,
3471 forked_by="gabriel",
3472 created_at=_now(days=25),
3473 ))
3474 # chen/film-score ← kevin_macleod/cinematic-strings
3475 db.add(MusehubFork(
3476 fork_id=_uid("fork-cinstrings-chen"),
3477 source_repo_id=REPO_CIN_STRINGS,
3478 fork_repo_id=REPO_FILM_SCORE,
3479 forked_by="chen",
3480 created_at=_now(days=22),
3481 ))
3482 # gabriel/community-collab ← bach/goldberg-variations
3483 db.add(MusehubFork(
3484 fork_id=_uid("fork-goldberg-gabriel-community"),
3485 source_repo_id=REPO_GOLDBERG,
3486 fork_repo_id=REPO_COMMUNITY,
3487 forked_by="gabriel",
3488 created_at=_now(days=20),
3489 ))
3490 print(" ✅ Forks: 7")
3491
3492 await db.flush()
3493
3494 # ── 16. View events (analytics) ───────────────────────────────
3495 # 1000+ total across all repos (community + genre archives). Each repo
3496 # receives 30 days of daily view fingerprints; active repos get up to 10
3497 # unique viewers per day, quieter repos get fewer.
3498 view_count = 0
3499 view_repos = list(REPOS) + list(GENRE_REPOS)
3500 for r in view_repos:
3501 repo_id = r["repo_id"]
3502 star_count = r.get("star_count", 5)
3503 for day_offset in range(30):
3504 date_str = (_now(days=day_offset)).strftime("%Y-%m-%d")
3505 viewers = max(star_count // 3 + 1, 3) # at least 3 unique viewers/day
3506 for v in range(min(viewers, 10)):
3507 try:
3508 db.add(MusehubViewEvent(
3509 view_id=_uid(f"view-{repo_id}-{day_offset}-{v}"),
3510 repo_id=repo_id,
3511 viewer_fingerprint=_sha(f"viewer-{repo_id}-{v}"),
3512 event_date=date_str,
3513 created_at=_now(days=day_offset),
3514 ))
3515 view_count += 1
3516 except Exception:
3517 pass
3518 print(f" ✅ View events: {view_count}")
3519
3520 await db.flush()
3521
3522 # ── 17. Download events ───────────────────────────────────────
3523 # 5-25 downloads per release tag across all repos. Using release_tags
3524 # (built in step 7) so every release gets at least 5 unique downloaders.
3525 all_user_ids = [u[0] for u in USERS]
3526 dl_count = 0
3527 dl_repos = list(REPOS) + list(GENRE_REPOS)
3528 for r in dl_repos:
3529 repo_id = r["repo_id"]
3530 tags = release_tags.get(repo_id, ["main"])
3531 if not tags:
3532 tags = ["main"]
3533 for ti, tag in enumerate(tags):
3534 # 5-15 downloads per release depending on tag index (older = more)
3535 n_downloads = max(5, 15 - ti * 2)
3536 for i in range(n_downloads):
3537 db.add(MusehubDownloadEvent(
3538 dl_id=_uid(f"dl2-{repo_id}-{tag}-{i}"),
3539 repo_id=repo_id,
3540 ref=tag,
3541 downloader_id=all_user_ids[i % len(all_user_ids)],
3542 created_at=_now(days=ti * 7 + i),
3543 ))
3544 dl_count += 1
3545 print(f" ✅ Download events: {dl_count}")
3546
3547 # ── 17b. Webhooks + deliveries (1-3/repo, 10-20 deliveries each) ──────────
3548 WEBHOOK_CONFIGS: list[tuple[str, list[str]]] = [
3549 # (url_suffix, events)
3550 ("push", ["push"]),
3551 ("pr", ["pull_request", "push"]),
3552 ("release", ["release", "push", "pull_request"]),
3553 ]
3554 # Delivery outcome patterns: (response_status, success)
3555 # Mix of 200 OK, 500 server error, and 0 timeout
3556 DELIVERY_OUTCOMES: list[tuple[int, bool, str]] = [
3557 (200, True, '{"status": "ok"}'),
3558 (200, True, '{"accepted": true}'),
3559 (200, True, '{"queued": true}'),
3560 (500, False, '{"error": "Internal Server Error"}'),
3561 (500, False, '{"error": "Service unavailable"}'),
3562 (0, False, ""), # timeout — no response
3563 (200, True, '{"ok": 1}'),
3564 (404, False, '{"error": "Not Found"}'),
3565 (200, True, '{"received": true}'),
3566 (0, False, ""), # timeout
3567 ]
3568 WEBHOOK_EVENT_TYPES = ["push", "pull_request", "release", "issue", "comment"]
3569
3570 webhook_count = 0
3571 delivery_count = 0
3572 for r in REPOS[:10]:
3573 repo_id = r["repo_id"]
3574 # 1-3 webhooks per repo based on repo index
3575 repo_idx = REPOS.index(r)
3576 n_webhooks = 1 + (repo_idx % 3)
3577 for wh_i in range(n_webhooks):
3578 url_suffix, events = WEBHOOK_CONFIGS[wh_i % len(WEBHOOK_CONFIGS)]
3579 wh_id = _uid(f"webhook-{repo_id}-{wh_i}")
3580 db.add(MusehubWebhook(
3581 webhook_id=wh_id,
3582 repo_id=repo_id,
3583 url=f"https://hooks.example.com/{r['owner']}/{r['slug']}/{url_suffix}",
3584 events=events,
3585 secret=_sha(f"secret-{repo_id}-{wh_i}")[:32],
3586 active=True,
3587 created_at=_now(days=r["days_ago"] - 5),
3588 ))
3589 webhook_count += 1
3590 # 10-20 deliveries per webhook
3591 n_deliveries = 10 + (abs(hash(wh_id)) % 11)
3592 for d_i in range(n_deliveries):
3593 outcome_idx = (abs(hash(wh_id)) + d_i) % len(DELIVERY_OUTCOMES)
3594 status, success, resp_body = DELIVERY_OUTCOMES[outcome_idx]
3595 evt = WEBHOOK_EVENT_TYPES[d_i % len(WEBHOOK_EVENT_TYPES)]
3596 db.add(MusehubWebhookDelivery(
3597 delivery_id=_uid(f"wh-delivery-{wh_id}-{d_i}"),
3598 webhook_id=wh_id,
3599 event_type=evt,
3600 payload=f'{{"event": "{evt}", "repo": "{repo_id}", "seq": {d_i}}}',
3601 attempt=1 + (d_i % 3),
3602 success=success,
3603 response_status=status,
3604 response_body=resp_body,
3605 delivered_at=_now(days=r["days_ago"] - d_i, hours=d_i % 24),
3606 ))
3607 delivery_count += 1
3608 print(f" ✅ Webhooks: {webhook_count} Deliveries: {delivery_count}")
3609
3610 await db.flush()
3611
3612 # ── 17c. Render Jobs (async audio render pipeline) ─────────────────────────
3613 # Creates completed, processing, and failed render jobs across repos.
3614 # Each job ties a commit to a set of generated MP3/piano-roll artifact IDs.
3615 _RENDER_STATUSES = ["completed", "completed", "completed", "processing", "failed"]
3616 render_job_count = 0
3617 for r in all_repos[:16]: # Community + most popular genre archive repos
3618 repo_id = r["repo_id"]
3619 commits = all_commits.get(repo_id, [])
3620 if not commits:
3621 continue
3622 # 2-3 render jobs per repo (latest commits)
3623 target_commits = commits[-3:]
3624 for ji, c in enumerate(target_commits):
3625 rj_cid: str = c["commit_id"]
3626 rj_status: str = _RENDER_STATUSES[(abs(hash(repo_id)) + ji) % len(_RENDER_STATUSES)]
3627 err_msg = "Storpheus timeout after 30s" if rj_status == "failed" else None
3628 mp3_ids = (
3629 [f"sha256:{_sha(f'mp3-{rj_cid}-{i}')}" for i in range(3)]
3630 if rj_status == "completed" else []
3631 )
3632 img_ids = (
3633 [f"sha256:{_sha(f'img-{rj_cid}-{i}')}" for i in range(2)]
3634 if rj_status == "completed" else []
3635 )
3636 try:
3637 db.add(MusehubRenderJob(
3638 render_job_id=_uid(f"rjob-{repo_id}-{rj_cid[:12]}"),
3639 repo_id=repo_id,
3640 commit_id=rj_cid,
3641 status=rj_status,
3642 error_message=err_msg,
3643 midi_count=len(REPO_TRACKS.get(REPO_KEY_MAP.get(repo_id, "neo-soul"),
3644 REPO_TRACKS["neo-soul"])),
3645 mp3_object_ids=mp3_ids,
3646 image_object_ids=img_ids,
3647 created_at=_now(days=ji + 1),
3648 updated_at=_now(days=ji),
3649 ))
3650 render_job_count += 1
3651 except Exception:
3652 pass # unique constraint on (repo_id, commit_id) — skip dupes
3653 print(f" ✅ Render jobs: {render_job_count}")
3654
3655 await db.flush()
3656
3657 # ── 17d. Activity Event Stream (musehub_events) ────────────────────────────
3658 # Captures the most recent activity on each repo: push, pr_open, pr_merge,
3659 # issue_open, issue_close, release, tag, session, fork, star events.
3660 _EVENT_TEMPLATES: list[tuple[str, str]] = [
3661 ("push", "pushed {n} commits to {branch}"),
3662 ("push", "force-pushed to {branch} (rebase)"),
3663 ("pr_open", "opened pull request: {title}"),
3664 ("pr_merge", "merged pull request into {branch}"),
3665 ("pr_close", "closed pull request without merging"),
3666 ("issue_open", "opened issue: {title}"),
3667 ("issue_close", "closed issue #{n}"),
3668 ("release", "published release {tag}"),
3669 ("tag", "created tag {tag} at {branch}"),
3670 ("session_start", "started recording session"),
3671 ("session_end", "ended recording session — {n} commits"),
3672 ("fork", "forked this repo"),
3673 ("star", "starred this repo"),
3674 ("branch_create", "created branch {branch}"),
3675 ]
3676 _EVENT_ACTORS_POOL = list(ALL_CONTRIBUTORS)
3677 event_count = 0
3678 for r in all_repos:
3679 repo_id = r["repo_id"]
3680 commits = all_commits.get(repo_id, [])
3681 owner = r["owner"]
3682 actor_pool = [owner] * 3 + _EVENT_ACTORS_POOL # Weight owner higher
3683 # Generate 8-15 events per repo spread over the last 60 days
3684 n_events = 8 + abs(hash(repo_id)) % 8
3685 for ei in range(n_events):
3686 tmpl_type, tmpl_body = _EVENT_TEMPLATES[ei % len(_EVENT_TEMPLATES)]
3687 actor = actor_pool[(abs(hash(repo_id)) + ei) % len(actor_pool)]
3688 n_val = (ei % 10) + 1
3689 branch = "main" if ei % 3 == 0 else f"feat/{r['slug'][:20]}-{ei}"
3690 tag = f"v{ei // 3 + 1}.0.{'0' if ei % 2 == 0 else '1'}"
3691 description = (
3692 tmpl_body
3693 .replace("{n}", str(n_val))
3694 .replace("{branch}", branch)
3695 .replace("{title}", f"Issue/PR title for event {ei}")
3696 .replace("{tag}", tag)
3697 )
3698 meta: dict[str, object] = {
3699 "actor": actor,
3700 "branch": branch,
3701 "commit_count": n_val,
3702 }
3703 if commits and ei < len(commits):
3704 meta["commit_id"] = commits[ei]["commit_id"]
3705 db.add(MusehubEvent(
3706 event_id=_uid(f"event-{repo_id}-{ei}"),
3707 repo_id=repo_id,
3708 event_type=tmpl_type,
3709 actor=actor,
3710 description=description,
3711 event_metadata=meta,
3712 created_at=_now(days=60 - ei * 4, hours=ei * 3 % 24),
3713 ))
3714 event_count += 1
3715 print(f" ✅ Events: {event_count}")
3716
3717 await db.flush()
3718
3719 # ── 18. Muse VCS — muse_objects, muse_snapshots, muse_commits, muse_tags ─
3720 #
3721 # Inserts content-addressed MIDI blobs, snapshot manifests, a proper DAG
3722 # of Muse commits (including merge commits), and the full tag taxonomy.
3723 #
3724 # Insertion order respects FK constraints:
3725 # muse_objects → muse_snapshots → muse_commits → muse_tags
3726 #
3727 muse_obj_count = 0
3728 muse_snap_count = 0
3729 muse_commit_count = 0
3730 muse_tag_count = 0
3731
3732 # Running objects pool so the same content can be deduplicated across
3733 # snapshots (object_ids that haven't changed reuse the same sha256).
3734 # Structure: repo_id → {filename: object_id}
3735 _prev_objects: dict[str, dict[str, str]] = {}
3736
3737 for r in REPOS:
3738 repo_id = r["repo_id"]
3739 hub_commits = all_commits.get(repo_id, [])
3740 if not hub_commits:
3741 continue
3742
3743 track_files = MUSE_VCS_FILES.get(repo_id, MUSE_VCS_FILES[REPO_AMBIENT])
3744 meta = MUSE_COMMIT_META.get(repo_id, MUSE_COMMIT_META[REPO_AMBIENT])
3745 is_rich = repo_id in MUSE_RICH_TAG_REPOS
3746
3747 prev_objects: dict[str, str] = {} # filename → object_id for this repo
3748 muse_commit_ids: list[str] = [] # ordered muse commit_ids for this repo
3749
3750 for i, hub_c in enumerate(hub_commits):
3751 snap_seed = f"snap-muse-{repo_id}-{i}"
3752 committed_at = hub_c["timestamp"]
3753
3754 # Build this commit's object set.
3755 # Every commit, ~2 files "change" (get fresh object_ids).
3756 # The rest reuse from the previous commit — simulating deduplication.
3757 changed_indices = {i % len(track_files), (i + 2) % len(track_files)}
3758 commit_objects: dict[str, str] = {}
3759
3760 for fi, (fname, base_size) in enumerate(track_files):
3761 if fi in changed_indices or fname not in prev_objects:
3762 # New or modified file → fresh content-addressed blob.
3763 obj_id = _sha(f"midi-{repo_id}-{fname}-v{i}")
3764 size = base_size + (i * 128) % 4096
3765 await db.execute(
3766 text(
3767 "INSERT INTO muse_objects (object_id, size_bytes, created_at)"
3768 " VALUES (:oid, :sz, :ca)"
3769 " ON CONFLICT (object_id) DO NOTHING"
3770 ),
3771 {"oid": obj_id, "sz": size, "ca": committed_at},
3772 )
3773 muse_obj_count += 1
3774 else:
3775 # Unchanged file → reuse previous object_id (deduplication).
3776 obj_id = prev_objects[fname]
3777 commit_objects[fname] = obj_id
3778
3779 prev_objects = commit_objects
3780
3781 # Snapshot — manifest maps track paths to object_ids.
3782 snapshot_id = _sha(snap_seed)
3783 manifest: dict[str, str] = {f"tracks/{fname}": oid for fname, oid in commit_objects.items()}
3784 await db.execute(
3785 text(
3786 "INSERT INTO muse_snapshots (snapshot_id, manifest, created_at)"
3787 " VALUES (:sid, :manifest, :ca)"
3788 " ON CONFLICT (snapshot_id) DO NOTHING"
3789 ),
3790 {"sid": snapshot_id, "manifest": json.dumps(manifest), "ca": committed_at},
3791 )
3792 muse_snap_count += 1
3793
3794 # Muse commit — derives its ID from snapshot + parent + message.
3795 parent_id: str | None = muse_commit_ids[-1] if muse_commit_ids else None
3796 # Merge commit every 7 commits (from commit 7 onward) — parent2 is the
3797 # commit from 5 positions back, simulating a merged feature branch.
3798 # Interval of 7 guarantees ≥5 merges per repo for repos with ≥35 commits.
3799 parent2_id: str | None = None
3800 if i >= 7 and i % 7 == 0 and len(muse_commit_ids) >= 6:
3801 parent2_id = muse_commit_ids[-6]
3802
3803 commit_id = _sha(f"muse-c-{snapshot_id}-{parent_id or ''}-{hub_c['message']}")
3804 await db.execute(
3805 text(
3806 "INSERT INTO muse_commits"
3807 " (commit_id, repo_id, branch, parent_commit_id, parent2_commit_id,"
3808 " snapshot_id, message, author, committed_at, created_at, metadata)"
3809 " VALUES"
3810 " (:cid, :rid, :branch, :pid, :p2id,"
3811 " :sid, :msg, :author, :cat, :cat, :meta)"
3812 " ON CONFLICT (commit_id) DO NOTHING"
3813 ),
3814 {
3815 "cid": commit_id,
3816 "rid": repo_id,
3817 "branch": hub_c["branch"],
3818 "pid": parent_id,
3819 "p2id": parent2_id,
3820 "sid": snapshot_id,
3821 "msg": hub_c["message"],
3822 "author": hub_c["author"],
3823 "cat": committed_at,
3824 "meta": json.dumps(meta),
3825 },
3826 )
3827 muse_commit_ids.append(commit_id)
3828 muse_commit_count += 1
3829
3830 # Tags: apply cycling taxonomy to every commit.
3831 # Rich repos get ALL taxonomy values; others get a representative subset.
3832 if is_rich:
3833 # Cycle through all 57 tag values across commits so every value appears.
3834 tag_val = _ALL_MUSE_TAGS[i % len(_ALL_MUSE_TAGS)]
3835 tag_vals = [tag_val]
3836 # Also add a second tag from a different category group.
3837 second_idx = (i + len(MUSE_EMOTION_TAGS)) % len(_ALL_MUSE_TAGS)
3838 if second_idx != i % len(_ALL_MUSE_TAGS):
3839 tag_vals.append(_ALL_MUSE_TAGS[second_idx])
3840 else:
3841 # Non-rich repos get one tag per commit drawn from a trimmed pool.
3842 _trimmed = MUSE_EMOTION_TAGS + MUSE_STAGE_TAGS + MUSE_GENRE_TAGS
3843 tag_vals = [_trimmed[i % len(_trimmed)]]
3844
3845 for tag_val in tag_vals:
3846 tag_id = _uid(f"muse-tag-{commit_id}-{tag_val}")
3847 await db.execute(
3848 text(
3849 "INSERT INTO muse_tags (tag_id, repo_id, commit_id, tag, created_at)"
3850 " VALUES (:tid, :rid, :cid, :tag, :ca)"
3851 " ON CONFLICT (tag_id) DO NOTHING"
3852 ),
3853 {"tid": tag_id, "rid": repo_id, "cid": commit_id,
3854 "tag": tag_val, "ca": committed_at},
3855 )
3856 muse_tag_count += 1
3857
3858 _prev_objects[repo_id] = prev_objects
3859
3860 # Ensure every tag taxonomy value appears at least once in REPO_NEO_SOUL.
3861 # Walk through ALL values and seed any that haven't been covered yet.
3862 if all_commits.get(REPO_NEO_SOUL):
3863 hub_commits_ns = all_commits[REPO_NEO_SOUL]
3864 muse_ids_ns: list[str] = []
3865 for i, hub_c in enumerate(hub_commits_ns):
3866 snap_seed = f"snap-muse-{REPO_NEO_SOUL}-{i}"
3867 snapshot_id = _sha(snap_seed)
3868 parent_id_ns: str | None = muse_ids_ns[-1] if muse_ids_ns else None
3869 commit_id_ns = _sha(f"muse-c-{snapshot_id}-{parent_id_ns or ''}-{hub_c['message']}")
3870 muse_ids_ns.append(commit_id_ns)
3871
3872 # Fetch existing tags for REPO_NEO_SOUL.
3873 result = await db.execute(
3874 text("SELECT tag FROM muse_tags WHERE repo_id = :rid"),
3875 {"rid": REPO_NEO_SOUL},
3876 )
3877 existing_tags: set[str] = {row[0] for row in result.fetchall()}
3878 missing_tags = [t for t in _ALL_MUSE_TAGS if t not in existing_tags]
3879
3880 for j, missing_tag in enumerate(missing_tags):
3881 commit_id_ns = muse_ids_ns[j % len(muse_ids_ns)]
3882 committed_at_ns = hub_commits_ns[j % len(hub_commits_ns)]["timestamp"]
3883 tag_id = _uid(f"muse-tag-fill-{REPO_NEO_SOUL}-{missing_tag}")
3884 await db.execute(
3885 text(
3886 "INSERT INTO muse_tags (tag_id, repo_id, commit_id, tag, created_at)"
3887 " VALUES (:tid, :rid, :cid, :tag, :ca)"
3888 " ON CONFLICT (tag_id) DO NOTHING"
3889 ),
3890 {"tid": tag_id, "rid": REPO_NEO_SOUL, "cid": commit_id_ns,
3891 "tag": missing_tag, "ca": committed_at_ns},
3892 )
3893 muse_tag_count += 1
3894
3895 await db.flush()
3896 print(f" ✅ Muse objects: {muse_obj_count} blobs")
3897 print(f" ✅ Muse snapshots: {muse_snap_count} manifests")
3898 print(f" ✅ Muse commits: {muse_commit_count} (DAG; includes merge commits)")
3899 print(f" ✅ Muse tags: {muse_tag_count} (full taxonomy)")
3900
3901 await db.flush()
3902
3903 # ── 20. Access Tokens (API key tracking) ──────────────────────────────────
3904 token_count = 0
3905 for uid, uname, _ in USERS:
3906 # 1-3 access tokens per user (active, revoked, expired)
3907 n_tokens = 1 + abs(hash(uid)) % 3
3908 for ti in range(n_tokens):
3909 is_revoked = ti > 0 and ti % 2 == 0
3910 db.add(AccessToken(
3911 id=_uid(f"token-{uid}-{ti}"),
3912 user_id=uid,
3913 token_hash=_sha(f"tok-{uid}-{ti}-secret"),
3914 expires_at=_now(days=-30 * ti) if is_revoked else _now(days=-365),
3915 revoked=is_revoked,
3916 created_at=_now(days=90 - ti * 15),
3917 ))
3918 token_count += 1
3919 print(f" ✅ Access tokens: {token_count}")
3920
3921 await db.flush()
3922
3923 await db.commit()
3924 print()
3925 _print_urls(all_commits, session_ids, pr_ids, release_tags)
3926
3927
3928 def _print_urls(
3929 all_commits: dict[str, list[dict[str, Any]]] | None = None,
3930 session_ids: dict[str, list[str]] | None = None,
3931 pr_ids: dict[str, list[str]] | None = None,
3932 release_tags: dict[str, list[str]] | None = None,
3933 ) -> None:
3934 """Print all seeded MuseHub URLs to stdout for manual browser verification."""
3935 BASE = "http://localhost:10001/musehub/ui"
3936 print()
3937 print("=" * 72)
3938 print("🎵 MUSEHUB — ALL URLs (localhost:10001)")
3939 print("=" * 72)
3940
3941 print("\n── User profiles ────────────────────────────────────────────────")
3942 for _, uname, _ in list(USERS) + list(COMPOSER_USERS):
3943 print(f" {BASE}/users/{uname}")
3944 print(f" {BASE}/{uname} (redirects → above)")
3945
3946 print("\n── Explore / discover ───────────────────────────────────────────")
3947 print(f" {BASE}/explore")
3948 print(f" {BASE}/trending")
3949 print(f" {BASE}/search")
3950 print(f" {BASE}/feed")
3951
3952 all_repos_for_urls = list(REPOS[:8]) + list(GENRE_REPOS) # skip private fork repos
3953 for r in all_repos_for_urls: # Skip fork repos from URL dump
3954 owner, slug = r["owner"], r["slug"]
3955 repo_id = r["repo_id"]
3956 rbase = f"{BASE}/{owner}/{slug}"
3957
3958 print(f"\n── {owner}/{slug} ─────────────────────────────────────")
3959 print(f" Repo: {rbase}")
3960 print(f" Graph: {rbase}/graph")
3961 print(f" Timeline: {rbase}/timeline")
3962 print(f" Insights: {rbase}/insights")
3963 print(f" Credits: {rbase}/credits")
3964 print(f" Search: {rbase}/search")
3965
3966 if all_commits and repo_id in all_commits:
3967 commits = all_commits[repo_id]
3968 for c in commits[-3:]:
3969 print(f" Commit: {rbase}/commits/{c['commit_id'][:12]}")
3970 if commits:
3971 print(f" Diff: {rbase}/commits/{commits[-1]['commit_id'][:12]}/diff")
3972
3973 print(f" Issues: {rbase}/issues")
3974 print(f" PRs: {rbase}/pulls")
3975 print(f" Releases: {rbase}/releases")
3976 if release_tags and repo_id in release_tags:
3977 for tag in release_tags[repo_id]:
3978 print(f" {rbase}/releases/{tag}")
3979 print(f" Sessions: {rbase}/sessions")
3980 if session_ids and repo_id in session_ids:
3981 for sid in session_ids[repo_id][:2]:
3982 print(f" {rbase}/sessions/{sid}")
3983 print(f" Divergence:{rbase}/divergence")
3984 print(f" Context: {rbase}/context/main")
3985 print(f" Analysis: {rbase}/analysis/main/contour")
3986 print(f" {rbase}/analysis/main/tempo")
3987 print(f" {rbase}/analysis/main/dynamics")
3988
3989 print()
3990 print("=" * 72)
3991 print("✅ Seed complete.")
3992 print("=" * 72)
3993
3994
3995 async def main() -> None:
3996 """CLI entry point. Pass --force to wipe existing seed data before re-seeding."""
3997 force = "--force" in sys.argv
3998 db_url: str = settings.database_url or ""
3999 engine = create_async_engine(db_url, echo=False)
4000 async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) # type: ignore[call-overload] # SQLAlchemy typing: sessionmaker + class_=AsyncSession overload not reflected in stubs
4001 async with async_session() as db:
4002 await seed(db, force=force)
4003 await engine.dispose()
4004
4005
4006 if __name__ == "__main__":
4007 asyncio.run(main())