"""MuseHub stress-test seed script. Inserts a rich, realistic dataset that exercises every implemented URL, every sidebar section, every social feature, every analytics panel, and every discovery/explore surface. Scale: - 8 users (gabriel, sofia, marcus, yuki, aaliya, chen, fatou, pierre) - 12 repos across 5 genres - 30-50 commits per repo (with realistic branch history) - 10+ issues per repo, mix of open/closed - 4+ PRs per repo (open, merged, closed) - 2-4 releases per repo - 3-8 sessions per repo - Comments, reactions, follows, watches, notifications, forks, view events - Commit objects (tracks) with real instrument roles for the breakdown bar Run inside the container: docker compose exec maestro python3 /app/scripts/seed_musehub.py Idempotent: pass --force to wipe and re-insert. """ from __future__ import annotations import asyncio import hashlib import json import sys import uuid from datetime import datetime, timedelta, timezone from typing import Any from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker from musehub.config import settings from musehub.contracts.json_types import NoteDict from musehub.db.models import ( AccessToken, Conversation, ConversationMessage, MessageAction, UsageLog, User, ) from musehub.db.muse_models import NoteChange, Phrase, Variation from musehub.db.musehub_collaborator_models import MusehubCollaborator from musehub.db.musehub_label_models import ( MusehubIssueLabel, MusehubLabel, MusehubPRLabel, ) from musehub.db.musehub_models import ( MusehubBranch, MusehubComment, MusehubCommit, MusehubDownloadEvent, MusehubEvent, MusehubFollow, MusehubFork, MusehubIssue, MusehubIssueComment, MusehubIssueMilestone, MusehubMilestone, MusehubNotification, MusehubObject, MusehubPRComment, MusehubPRReview, MusehubProfile, MusehubPullRequest, MusehubReaction, MusehubRelease, MusehubReleaseAsset, MusehubRenderJob, MusehubRepo, MusehubSession, MusehubStar, MusehubViewEvent, MusehubWatch, MusehubWebhook, MusehubWebhookDelivery, ) from musehub.db.musehub_stash_models import MusehubStash, MusehubStashEntry # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- UTC = timezone.utc def _now(days: int = 0, hours: int = 0) -> datetime: return datetime.now(tz=UTC) - timedelta(days=days, hours=hours) def _sha(seed: str) -> str: return hashlib.sha256(seed.encode()).hexdigest() def _uid(seed: str) -> str: return str(uuid.UUID(bytes=hashlib.md5(seed.encode()).digest())) # --------------------------------------------------------------------------- # Constants — stable IDs so URLs never change between re-seeds # --------------------------------------------------------------------------- # Users — original community GABRIEL = "user-gabriel-001" SOFIA = "user-sofia-002" MARCUS = "user-marcus-003" YUKI = "user-yuki-004" AALIYA = "user-aaliya-005" CHEN = "user-chen-006" FATOU = "user-fatou-007" PIERRE = "user-pierre-008" USERS = [ (GABRIEL, "gabriel", "Composer & producer. Neo-soul, modal jazz, ambient. All sounds generated by Stori."), (SOFIA, "sofia", "Classical-meets-electronic. Algorithmic composition. Lover of Nils Frahm."), (MARCUS, "marcus", "Session keys player turned digital composer. Jazz, funk, everything in between."), (YUKI, "yuki", "Tokyo-based sound designer. Granular synthesis, algorithmic rhythms, and noise art."), (AALIYA, "aaliya", "Afrobeat, highlife, and jazz fusion. Lagos → Berlin. Always in motion."), (CHEN, "chen", "Microtonal explorer. Just intonation, spectral music, extended techniques."), (FATOU, "fatou", "Griots meet Moog. West African rhythms with modular synthesis."), (PIERRE, "pierre", "French chanson meets minimalism. Piano, cello, and long silences."), ] # Users — historical composers and licensed artists (batch-13) BACH = "user-bach-000000009" CHOPIN = "user-chopin-00000010" SCOTT_JOPLIN = "user-sjoplin-0000011" KEVIN_MACLEOD = "user-kmacleod-000012" KAI_ENGEL = "user-kaiengl-000013" COMPOSER_USERS = [ (BACH, "bach", "Johann Sebastian Bach (1685-1750). Baroque counterpoint, fugue, and harmony. Archive upload by Stori community."), (CHOPIN, "chopin", "Frédéric Chopin (1810-1849). Romantic piano. Nocturnes, études, ballades. Archive upload by Stori community."), (SCOTT_JOPLIN, "scott_joplin", "Scott Joplin (1868-1917). King of Ragtime. Maple Leaf Rag, The Entertainer. Archive upload by Stori community."), (KEVIN_MACLEOD, "kevin_macleod", "Kevin MacLeod. Cinematic, orchestral, ambient. Thousands of royalty-free compositions. incompetech.com."), (KAI_ENGEL, "kai_engel", "Kai Engel. Ambient, neoclassical, cinematic. Delicate textures and patient melodies. Free Music Archive."), ] # Rich profile metadata for all users — display names, locations, CC attribution, social links. # Keyed by user_id (stable VARCHAR(36) identifier). Used when seeding musehub_profiles. # CC attribution: "Public Domain" for composers expired >70 yrs; "CC BY 4.0" for explicitly licensed artists. PROFILE_DATA: dict[str, dict[str, str | bool | None]] = { GABRIEL: dict( display_name="Gabriel", bio="Building the infinite music machine. Neo-baroque meets modern production. Every session is a new fugue.", location="San Francisco, CA", website_url="https://stori.music", twitter_handle="gabrielstori", is_verified=False, cc_license=None, ), SOFIA: dict( display_name="Sofia", bio="Counterpoint scholar and baroque revival composer. Polyphony enthusiast. Bach is the north star.", location="Vienna, Austria", website_url="https://sofia.stori.music", twitter_handle="sofia_counterpoint", is_verified=False, cc_license=None, ), MARCUS: dict( display_name="Marcus", bio="EDM producer. Sampling the classics. 808s and Scarlatti. Ragtime breakbeats are a real genre now.", location="Detroit, MI", website_url="https://marcus.stori.music", twitter_handle="marcus_808", is_verified=False, cc_license=None, ), YUKI: dict( display_name="Yuki", bio="Music theorist and Muse power user. Harmonic analysis obsessive. Every chord has a reason.", location="Tokyo, Japan", website_url="https://yuki.stori.music", twitter_handle="yuki_harmony", is_verified=False, cc_license=None, ), AALIYA: dict( display_name="Aaliya", bio="Jazz fusion meets romantic piano. Coltrane changes on Chopin. Lagos-born, Berlin-based.", location="Berlin, Germany", website_url="https://aaliya.stori.music", twitter_handle="aaliya_jazzpiano", is_verified=False, cc_license=None, ), CHEN: dict( display_name="Chen", bio="Film composer. Every emotion has a chord. Every scene has a theme. Microtonal when the script demands.", location="Shanghai, China", website_url="https://chen.stori.music", twitter_handle="chen_filmscore", is_verified=False, cc_license=None, ), FATOU: dict( display_name="Fatou", bio="Afrobeats composer. Polyrhythm is natural. 7 over 4 makes sense to me. Griot traditions meet modular synthesis.", location="Dakar, Senegal", website_url="https://fatou.stori.music", twitter_handle="fatou_polyrhythm", is_verified=False, cc_license=None, ), PIERRE: dict( display_name="Pierre", bio="French chanson meets minimalism. Piano, cello, and long silences. Satie would approve.", location="Paris, France", website_url="https://pierre.stori.music", twitter_handle="pierre_chanson", is_verified=False, cc_license=None, ), BACH: dict( display_name="Johann Sebastian Bach", bio="Baroque composer. 48 preludes, 48 fugues. All 24 keys. Music is the arithmetic of the soul.", location="Leipzig, Saxony (1723–1750)", website_url="https://www.bach-digital.de", twitter_handle=None, is_verified=True, cc_license="Public Domain", ), CHOPIN: dict( display_name="Frédéric Chopin", bio="Romantic pianist. Nocturnes, ballades, études. Expressive beyond measure. The piano speaks in my voice.", location="Paris, France (1831–1849)", website_url="https://chopin.nifc.pl", twitter_handle=None, is_verified=True, cc_license="Public Domain", ), SCOTT_JOPLIN: dict( display_name="Scott Joplin", bio="King of Ragtime. Maple Leaf Rag. The Entertainer. Syncopation is poetry in motion.", location="Sedalia, Missouri (1890s)", website_url="https://www.scottjoplin.org", twitter_handle=None, is_verified=True, cc_license="Public Domain", ), KEVIN_MACLEOD: dict( display_name="Kevin MacLeod", bio="Prolific composer. Every genre. Royalty-free forever. CC BY 4.0. If you use my music, just credit me.", location="Sandpoint, Idaho", website_url="https://incompetech.com", twitter_handle="kmacleod", is_verified=True, cc_license="CC BY 4.0", ), KAI_ENGEL: dict( display_name="Kai Engel", bio="Ambient architect. Long-form textures. Silence is also music. Free Music Archive.", location="Germany", website_url="https://freemusicarchive.org/music/Kai_Engel", twitter_handle=None, is_verified=True, cc_license="CC BY 4.0", ), } # All contributors for community-collab cycling (8 existing users) ALL_CONTRIBUTORS = [ "gabriel", "sofia", "marcus", "yuki", "aaliya", "chen", "fatou", "pierre", ] # Repos — original community projects REPO_NEO_SOUL = "repo-neo-soul-00000001" REPO_MODAL_JAZZ = "repo-modal-jazz-000001" REPO_AMBIENT = "repo-ambient-textures-1" REPO_AFROBEAT = "repo-afrobeat-grooves-1" REPO_MICROTONAL = "repo-microtonal-etudes1" REPO_DRUM_MACHINE = "repo-drum-machine-00001" REPO_CHANSON = "repo-chanson-minimale-1" REPO_GRANULAR = "repo-granular-studies-1" REPO_FUNK_SUITE = "repo-funk-suite-0000001" REPO_JAZZ_TRIO = "repo-jazz-trio-0000001" REPO_NEO_SOUL_FORK = "repo-neo-soul-fork-0001" REPO_AMBIENT_FORK = "repo-ambient-fork-0001" # Repos — 12 genre archive repos (batch-13) REPO_WTC = "repo-well-tempered-cl01" # bach/well-tempered-clavier REPO_GOLDBERG = "repo-goldberg-vars00001" # bach/goldberg-variations REPO_NOCTURNES = "repo-chopin-nocturnes01" # chopin/nocturnes REPO_MAPLE_LEAF = "repo-maple-leaf-rag0001" # scott_joplin/maple-leaf-rag REPO_CIN_STRINGS = "repo-cinematic-strngs01" # kevin_macleod/cinematic-strings REPO_KAI_AMBIENT = "repo-kai-ambient-txtr01" # kai_engel/ambient-textures REPO_NEO_BAROQUE = "repo-neo-baroque-000001" # gabriel/neo-baroque REPO_JAZZ_CHOPIN = "repo-jazz-chopin-000001" # aaliya/jazz-chopin REPO_RAGTIME_EDM = "repo-ragtime-edm-000001" # marcus/ragtime-edm REPO_FILM_SCORE = "repo-film-score-000001" # chen/film-score REPO_POLYRHYTHM = "repo-polyrhythm-000001" # fatou/polyrhythm REPO_COMMUNITY = "repo-community-collab01" # gabriel/community-collab REPOS: list[dict[str, Any]] = [ dict(repo_id=REPO_NEO_SOUL, name="Neo-Soul Experiment", owner="gabriel", slug="neo-soul-experiment", owner_user_id=GABRIEL, visibility="public", description="A funk-influenced neo-soul project exploring polyrhythmic grooves in F# minor.", tags=["neo-soul", "funk", "F# minor", "polyrhythm", "bass-heavy"], key_signature="F# minor", tempo_bpm=92, days_ago=90, star_count=24, fork_count=3), dict(repo_id=REPO_MODAL_JAZZ, name="Modal Jazz Sketches", owner="gabriel", slug="modal-jazz-sketches", owner_user_id=GABRIEL, visibility="public", description="Exploring Dorian and Phrygian modes. Miles Davis and Coltrane are the north stars.", tags=["jazz", "modal", "Dorian", "Phrygian", "piano", "trumpet"], key_signature="D Dorian", tempo_bpm=120, days_ago=60, star_count=18, fork_count=2), dict(repo_id=REPO_AMBIENT, name="Ambient Textures Vol. 1", owner="sofia", slug="ambient-textures-vol-1", owner_user_id=SOFIA, visibility="public", description="Slow-evolving pads and generative arpeggios. Inspired by Eno and Nils Frahm.", tags=["ambient", "generative", "pads", "Eb major", "slow"], key_signature="Eb major", tempo_bpm=60, days_ago=45, star_count=31, fork_count=5), dict(repo_id=REPO_AFROBEAT, name="Afrobeat Grooves", owner="aaliya", slug="afrobeat-grooves", owner_user_id=AALIYA, visibility="public", description="High-life meets contemporary production. Polyrhythmic percussion layers.", tags=["afrobeat", "highlife", "polyrhythm", "Lagos", "percussion"], key_signature="G major", tempo_bpm=128, days_ago=30, star_count=42, fork_count=6), dict(repo_id=REPO_MICROTONAL, name="Microtonal Études", owner="chen", slug="microtonal-etudes", owner_user_id=CHEN, visibility="public", description="31-TET explorations. Spectral harmony and just intonation studies.", tags=["microtonal", "spectral", "31-TET", "just intonation", "experimental"], key_signature="C (31-TET)", tempo_bpm=76, days_ago=25, star_count=9, fork_count=1), dict(repo_id=REPO_DRUM_MACHINE, name="808 Variations", owner="fatou", slug="808-variations", owner_user_id=FATOU, visibility="public", description="West African polyrhythm patterns through an 808 and modular synthesis.", tags=["drums", "808", "polyrhythm", "West Africa", "modular"], key_signature="A minor", tempo_bpm=100, days_ago=20, star_count=15, fork_count=2), dict(repo_id=REPO_CHANSON, name="Chanson Minimale", owner="pierre", slug="chanson-minimale", owner_user_id=PIERRE, visibility="public", description="French chanson miniatures. Piano and cello. Silence as a compositional element.", tags=["chanson", "minimalism", "piano", "cello", "French"], key_signature="A major", tempo_bpm=52, days_ago=15, star_count=7, fork_count=0), dict(repo_id=REPO_GRANULAR, name="Granular Studies", owner="yuki", slug="granular-studies", owner_user_id=YUKI, visibility="public", description="Granular synthesis research — texture, density, scatter. Source material: found sounds.", tags=["granular", "synthesis", "experimental", "Tokyo", "texture"], key_signature="E minor", tempo_bpm=70, days_ago=10, star_count=12, fork_count=1), dict(repo_id=REPO_FUNK_SUITE, name="Funk Suite No. 1", owner="marcus", slug="funk-suite-no-1", owner_user_id=MARCUS, visibility="public", description="A four-movement funk suite. Electric piano, clavinet, wah bass, and pocket drums.", tags=["funk", "electric piano", "clavinet", "groove", "suite"], key_signature="E minor", tempo_bpm=108, days_ago=50, star_count=28, fork_count=4), dict(repo_id=REPO_JAZZ_TRIO, name="Jazz Trio Sessions", owner="marcus", slug="jazz-trio-sessions", owner_user_id=MARCUS, visibility="public", description="Live-feel jazz trio recordings. Piano, double bass, brushed snare. Standards reimagined.", tags=["jazz", "trio", "piano", "bass", "standards"], key_signature="Bb major", tempo_bpm=138, days_ago=35, star_count=19, fork_count=2), # Forked repos (private — for the fork sidebar section) dict(repo_id=REPO_NEO_SOUL_FORK, name="Neo-Soul Experiment", owner="marcus", slug="neo-soul-experiment", owner_user_id=MARCUS, visibility="private", description="Fork of gabriel/neo-soul-experiment — Marcus's arrangement experiments.", tags=["neo-soul", "funk", "F# minor", "fork"], key_signature="F# minor", tempo_bpm=92, days_ago=10, star_count=0, fork_count=0), dict(repo_id=REPO_AMBIENT_FORK, name="Ambient Textures Vol. 1", owner="yuki", slug="ambient-textures-vol-1", owner_user_id=YUKI, visibility="private", description="Fork of sofia/ambient-textures-vol-1 — Yuki's granular re-imagining.", tags=["ambient", "granular", "fork"], key_signature="Eb major", tempo_bpm=60, days_ago=5, star_count=0, fork_count=0), ] # Genre archive repos — batch-13 additions with structured muse_tags GENRE_REPOS: list[dict[str, Any]] = [ dict(repo_id=REPO_WTC, name="The Well-Tempered Clavier", owner="bach", slug="well-tempered-clavier", owner_user_id=BACH, visibility="public", description="Bach's 48 preludes and fugues — one in each major and minor key. The definitive study in tonal harmony.", tags=["genre:baroque", "key:C", "key:Am", "key:G", "key:F", "key:Bb", "stage:released", "emotion:serene", "emotion:complex"], key_signature="C major (all 24 keys)", tempo_bpm=72, days_ago=365, star_count=88, fork_count=12), dict(repo_id=REPO_GOLDBERG, name="Goldberg Variations", owner="bach", slug="goldberg-variations", owner_user_id=BACH, visibility="public", description="Aria with 30 variations. Bach's monumental keyboard work — from simple canon to ornate arabesque.", tags=["genre:baroque", "stage:released", "emotion:joyful", "emotion:melancholic", "key:G"], key_signature="G major", tempo_bpm=60, days_ago=350, star_count=74, fork_count=9), dict(repo_id=REPO_NOCTURNES, name="Nocturnes", owner="chopin", slug="nocturnes", owner_user_id=CHOPIN, visibility="public", description="21 nocturnes for solo piano — poetry in sound. Lyrical melodies over arpeggiated left-hand accompaniment.", tags=["genre:romantic", "emotion:melancholic", "emotion:tender", "stage:released", "key:Bb", "key:Eb"], key_signature="Bb minor", tempo_bpm=58, days_ago=300, star_count=62, fork_count=8), dict(repo_id=REPO_MAPLE_LEAF, name="Maple Leaf Rag", owner="scott_joplin", slug="maple-leaf-rag", owner_user_id=SCOTT_JOPLIN, visibility="public", description="The rag that launched a revolution. Syncopated right-hand melody over an oom-pah bass. The birth of ragtime.", tags=["genre:ragtime", "emotion:playful", "stage:released", "key:Ab", "tempo:march"], key_signature="Ab major", tempo_bpm=100, days_ago=280, star_count=51, fork_count=7), dict(repo_id=REPO_CIN_STRINGS, name="Cinematic Strings", owner="kevin_macleod", slug="cinematic-strings", owner_user_id=KEVIN_MACLEOD, visibility="public", description="Orchestral string textures for cinematic use. Builds from delicate pianissimo to full tutti climax.", tags=["genre:cinematic", "emotion:triumphant", "stage:released", "tempo:adagio", "key:D"], key_signature="D minor", tempo_bpm=64, days_ago=180, star_count=43, fork_count=5), dict(repo_id=REPO_KAI_AMBIENT, name="Ambient Textures", owner="kai_engel", slug="ambient-textures", owner_user_id=KAI_ENGEL, visibility="public", description="Patient, breathing soundscapes. Piano, strings, and silence woven into evolving ambient fields.", tags=["genre:ambient", "emotion:serene", "stage:released", "tempo:largo", "key:C"], key_signature="C major", tempo_bpm=50, days_ago=150, star_count=38, fork_count=4), dict(repo_id=REPO_NEO_BAROQUE, name="Neo-Baroque Studies", owner="gabriel", slug="neo-baroque", owner_user_id=GABRIEL, visibility="public", description="What if Bach wrote jazz? Modal harmony and quartal voicings dressed in baroque counterpoint.", tags=["genre:baroque", "genre:jazz", "stage:rough-mix", "emotion:complex", "ref:bach", "key:D"], key_signature="D Dorian", tempo_bpm=84, days_ago=120, star_count=29, fork_count=3), dict(repo_id=REPO_JAZZ_CHOPIN, name="Jazz Chopin", owner="aaliya", slug="jazz-chopin", owner_user_id=AALIYA, visibility="public", description="Chopin nocturnes reharmonized through a Coltrane lens. Rootless voicings, tritone substitutions, and more.", tags=["genre:jazz", "genre:romantic", "emotion:tender", "ref:chopin", "ref:coltrane", "stage:mixing"], key_signature="Bb minor", tempo_bpm=68, days_ago=90, star_count=34, fork_count=4), dict(repo_id=REPO_RAGTIME_EDM, name="Ragtime EDM", owner="marcus", slug="ragtime-edm", owner_user_id=MARCUS, visibility="public", description="Scott Joplin meets the dancefloor. Syncopated MIDI melodies over trap hi-hats and house kick patterns.", tags=["genre:edm", "genre:ragtime", "stage:production", "emotion:playful", "tempo:dance"], key_signature="Ab major", tempo_bpm=128, days_ago=70, star_count=26, fork_count=3), dict(repo_id=REPO_FILM_SCORE, name="Film Score — Untitled", owner="chen", slug="film-score", owner_user_id=CHEN, visibility="public", description="Three-act cinematic score. Microtonal tension in Act I, spectral climax in Act II, resolution in Act III.", tags=["genre:cinematic", "emotion:tense", "emotion:triumphant", "stage:mixing", "key:C"], key_signature="C (31-TET)", tempo_bpm=72, days_ago=55, star_count=18, fork_count=2), dict(repo_id=REPO_POLYRHYTHM, name="Polyrhythm Studies", owner="fatou", slug="polyrhythm", owner_user_id=FATOU, visibility="public", description="West African rhythmic philosophy through a modular lens. 7-over-4, 5-over-3, and beyond.", tags=["genre:afrobeats", "emotion:playful", "emotion:energetic", "stage:rough-mix", "tempo:polyrhythm"], key_signature="A minor", tempo_bpm=92, days_ago=35, star_count=21, fork_count=2), dict(repo_id=REPO_COMMUNITY, name="Community Collab", owner="gabriel", slug="community-collab", owner_user_id=GABRIEL, visibility="public", description="An open canvas for all eight contributors. Every genre, every voice, one evolving composition.", tags=["genre:baroque", "genre:jazz", "genre:romantic", "genre:ragtime", "genre:cinematic", "genre:ambient", "genre:edm", "genre:afrobeats", "emotion:serene", "emotion:complex", "emotion:joyful", "emotion:melancholic", "emotion:tender", "emotion:playful", "emotion:energetic", "emotion:triumphant", "emotion:tense", "stage:rough-mix"], key_signature="C major", tempo_bpm=90, days_ago=200, star_count=95, fork_count=15), ] # --------------------------------------------------------------------------- # Commit templates per repo # --------------------------------------------------------------------------- def _make_commits(repo_id: str, repo_key: str, n: int) -> list[dict[str, Any]]: """Generate n realistic commits for repo_key with a branching history.""" TEMPLATES = { "neo-soul": [ ("init: establish F# minor groove template at 92 BPM", "gabriel"), ("feat(bass): add polyrhythmic bass line — 3-against-4 pulse", "gabriel"), ("feat(keys): Rhodes chord voicings with upper-structure triads", "gabriel"), ("refactor(drums): humanize ghost notes, tighten hi-hat velocity", "gabriel"), ("feat(strings): bridge string section — section:bridge track:strings", "gabriel"), ("feat(horns): sketch trumpet + alto sax counter-melody", "gabriel"), ("fix(keys): resolve voice-leading parallel fifths in bar 7", "gabriel"), ("feat(guitar): add scratch guitar rhythm in chorus — track:guitar", "gabriel"), ("refactor(bass): tighten sub-bass at bar 13 to avoid muddiness", "marcus"), ("feat(perc): add shaker and tambourine for groove density", "gabriel"), ("fix(timing): realign hi-hat to quantize grid after humanize", "gabriel"), ("feat(choir): add background vocal pad — ooh/aah — section:chorus", "gabriel"), ("refactor(mix): reduce Rhodes level -3dB, open hi-hat +2dB", "marcus"), ("feat(bridge): call-and-response horn arrangement — bars 25-32", "gabriel"), ("fix(harmony): correct augmented chord spelling in turnaround", "gabriel"), ("feat(strings): counterpoint violin line against bass in verse", "gabriel"), ("refactor(drums): add kick variation in bar 4 of each 8-bar phrase", "marcus"), ("feat(organ): add organ swell in pre-chorus — track:organ", "gabriel"), ("fix(keys): remove accidental octave doubling in Rhodes voicing", "gabriel"), ("feat(bass): slap variation for funk breakdown — section:breakdown", "marcus"), ("refactor(horns): rewrite alto sax response phrase — cleaner contour", "gabriel"), ("feat(perc): cowbell accent on beat 3 of bar 2 — groove:funk", "gabriel"), ("feat(strings): pizzicato countermelody — bars 17-24", "gabriel"), ("fix(guitar): tighten wah envelope attack — reduce pre-delay", "gabriel"), ("feat(voice): lead vocal melody sketch — section:verse track:vocals", "gabriel"), ], "modal-jazz": [ ("init: D Dorian vamp at 120 BPM — piano + bass", "gabriel"), ("feat(melody): Coltrane-inspired pentatonic runs over IV chord", "gabriel"), ("feat(drums): brush kit — swing factor 0.65", "gabriel"), ("experiment: Phrygian dominant bridge — E Phrygian Dominant", "gabriel"), ("feat(piano): add McCoy Tyner quartal voicings in A section", "gabriel"), ("fix(bass): correct walking bass note on bar 9 beat 3", "gabriel"), ("feat(trumpet): head melody sketch — 12-bar AABA form", "gabriel"), ("refactor(drums): increase ride cymbal bell accent frequency", "gabriel"), ("feat(piano): bebop left-hand comp pattern — bars 1-8", "marcus"), ("fix(melody): resolve blue note to major 3rd at phrase end", "gabriel"), ("feat(bass): pedal point through Phrygian section — E pedal", "gabriel"), ("feat(drums): hi-hat splash on beat 4 of turnaround", "gabriel"), ("refactor(piano): revoice III chord as tritone substitution", "gabriel"), ("feat(guitar): Freddie Green-style chord stabs — 4-to-the-bar", "marcus"), ("fix(trumpet): fix pitch of low C# — use Eb enharmonic", "gabriel"), ("feat(piano): out-chorus with McCoy quartal clusters", "gabriel"), ("feat(bass): counter-rhythm 2-bar fill after trumpet solo", "gabriel"), ], "ambient": [ ("init: Eb major pad foundation — slow attack 4s release 8s", "sofia"), ("feat(arp): generative 16th-note arpeggiator — random seed 42", "sofia"), ("feat(texture): granular string texture layer — section:intro", "yuki"), ("fix(arp): reduce velocity variance — sounds too mechanical", "sofia"), ("feat(pad): add sub-octave layer for warmth — section:middle", "sofia"), ("feat(bells): wind chime texture — 7th partial harmonic series", "yuki"), ("refactor(arp): increase note-length randomization range", "sofia"), ("feat(drone): Eb pedal drone — bowed brass harmonic", "pierre"), ("fix(texture): reduce granular density in intro — too busy", "yuki"), ("feat(reverb): add convolution reverb impulse — Norwegian church", "sofia"), ("feat(pad): second pad layer — inversion of root chord", "sofia"), ("refactor(arp): change arpeggio direction — ascending + descending", "sofia"), ("feat(texture): filtered noise texture — high shelf +6dB", "yuki"), ("fix(drone): tune drone to equal temperament Eb", "sofia"), ("feat(melody): sparse piano melody — whole notes — section:climax", "pierre"), ("feat(fade): 3-minute fade out — linear to -80dB", "sofia"), ("refactor(mix): reduce string texture -2dB to sit behind pad", "yuki"), ("fix(arp): fix stuck note at bar 64 — midi note-off missing", "sofia"), ], "afrobeat": [ ("init: G major groove at 128 BPM — 12/8 polyrhythm", "aaliya"), ("feat(perc): traditional talking drum pattern — track:tama", "aaliya"), ("feat(guitar): highlife guitar pattern — interlocking rhythm", "aaliya"), ("feat(bass): electric bass groove — root-fifth walking pattern", "aaliya"), ("feat(horns): brass unison figure — bars 1-4", "aaliya"), ("refactor(perc): tighten conga timing — reduce humanize variance", "fatou"), ("feat(keys): Fender Rhodes stabs — track:keys", "aaliya"), ("fix(guitar): fix choke on open string — add palm mute", "aaliya"), ("feat(choir): call-and-response vocal arrangement", "aaliya"), ("feat(bass): syncopated fills at section transitions", "aaliya"), ("refactor(horns): split alto and tenor lines — 3rd apart", "aaliya"), ("feat(perc): shekere layer — steady eighth-note pulse", "fatou"), ("fix(mix): reduce vocal level in verse — instrumental focus", "aaliya"), ("feat(guitar): second guitar — rhythmic scratches on offbeat", "aaliya"), ("feat(bass): slap bass hook for chorus energy boost", "aaliya"), ("refactor(drums): add more snare ghost notes — Questlove style", "fatou"), ("feat(keys): organ swell into chorus — track:organ", "aaliya"), ("fix(perc): fix timing drift on conga in bar 32", "fatou"), ("feat(strings): string overdub — Fela-inspired octave line", "aaliya"), ("feat(voice): lead vocal melody — Yoruba lyric sketch", "aaliya"), ], "microtonal": [ ("init: C (31-TET) drone exploration at 76 BPM", "chen"), ("feat(harmony): otonal hexad — 4:5:6:7:9:11", "chen"), ("feat(melody): quarter-tone scale ascending line", "chen"), ("fix(tuning): correct 7th partial — was off by 3 cents", "chen"), ("feat(rhythm): Messiaen mode 3 rhythm grid", "chen"), ("feat(texture): spectral filtered noise — harmonic series", "chen"), ("refactor(melody): retrograde inversion of opening motif", "chen"), ("feat(bass): undertone series pedal — utonal foundation", "chen"), ("fix(harmony): resolve voice-leading microtonal step error", "chen"), ("feat(perc): non-retrogradable rhythm in timpani", "chen"), ("feat(strings): col legno battuto technique — quarter-tone gliss", "chen"), ("refactor(harmony): substitute Ptolemy's intense chromatic", "chen"), ("feat(woodwinds): multiphonics — clarinet + flute", "chen"), ("fix(tuning): recalibrate piano to equal 31-TET temperament", "chen"), ], "drums": [ ("init: A minor 808 foundation at 100 BPM", "fatou"), ("feat(kick): four-on-the-floor with sub-frequency duck", "fatou"), ("feat(snare): syncopated snare with flam accent", "fatou"), ("feat(hihat): 16th-note hi-hat with velocity curve", "fatou"), ("feat(perc): djembe pattern — traditional Mandinka rhythm", "fatou"), ("feat(808): 808 bass note on root — 100ms decay", "fatou"), ("refactor(kick): tune 808 kick to key center A — 110Hz", "fatou"), ("fix(hihat): remove double-triggered hi-hat on beat 3", "fatou"), ("feat(perc): shaker accent pattern — offbeat sixteenths", "fatou"), ("feat(snare): ghost note velocity humanize — ±12 velocity", "fatou"), ("feat(808): sub-bass movement — root to 5th fills", "fatou"), ("refactor(perc): layer djembe with finger drum machine", "fatou"), ("feat(crash): crash on bar 9 downbeat — section transition", "fatou"), ], "chanson": [ ("init: A major sketch — piano solo motif at 52 BPM", "pierre"), ("feat(piano): left-hand ostinato — arpeggiated 9th chord", "pierre"), ("feat(cello): pizzicato bass line — bars 1-8", "pierre"), ("feat(piano): theme A — 8-bar melody in upper voice", "pierre"), ("feat(cello): sustained cello counterpoint — bars 9-16", "pierre"), ("refactor(piano): reduce left-hand density — let melody breathe", "pierre"), ("feat(piano): theme B in relative minor — F# minor", "pierre"), ("fix(cello): bowings — ensure smooth legato at bar 12", "pierre"), ("feat(piano): coda — augmented theme A in parallel 10ths", "pierre"), ("feat(silence): 4-bar rest before final chord — dynamic contrast", "pierre"), ("feat(cello): col legno tremolo — extended technique", "pierre"), ("refactor(harmony): substitute V7 with bVII for chanson flavour", "pierre"), ], "granular": [ ("init: E minor granular pad — source: rain recording", "yuki"), ("feat(scatter): random scatter algorithm — grain size 20-80ms", "yuki"), ("feat(density): grain density envelope — sparse to dense", "yuki"), ("feat(pitch): pitch randomization ±0.3 semitones", "yuki"), ("feat(texture): city ambience layer — Tokyo train station", "yuki"), ("fix(phase): fix grain phase correlation — reduce flamming", "yuki"), ("feat(filter): formant filter on granular output — vowel morph", "yuki"), ("refactor(scatter): increase random seed variation per bar", "yuki"), ("feat(rhythm): rhythmic granular — sync to 70 BPM sixteenths", "yuki"), ("fix(tuning): retune pitch center to E — was detuned +0.5st", "yuki"), ("feat(reverb): 8-second hall reverb tail — late reflections only", "yuki"), ("feat(mod): LFO modulation on grain position — 0.3Hz triangle", "yuki"), ], "funk-suite": [ ("init: E minor funk groove at 108 BPM — Mvt. I", "marcus"), ("feat(bass): wah-wah bass hook — bars 1-4 — track:bass", "marcus"), ("feat(keys): electric piano chord voicings — tight stabs", "marcus"), ("feat(clavinet): clavinet riff — bars 5-8", "marcus"), ("feat(drums): pocket drum groove — ghost notes on snare", "marcus"), ("feat(guitar): rhythm guitar — interlocking with clavinet", "marcus"), ("feat(horns): brass hits on the upbeat — track:horns", "marcus"), ("refactor(bass): tighten wah envelope attack for more snap", "marcus"), ("feat(keys): Rhodes solo in Mvt. II — Dorian mode", "marcus"), ("fix(guitar): remove string buzz on open D", "marcus"), ("feat(bass): slap funk breakdown — Mvt. II outro", "marcus"), ("feat(perc): add congas — Afro-Cuban polyrhythm layer", "marcus"), ("feat(keys): B3 organ swell — Mvt. III transition", "marcus"), ("refactor(drums): accent hi-hat on the e's — open 16th feel", "marcus"), ("fix(horns): retune brass — flat by 8 cents on high notes", "marcus"), ("feat(bass): octave bass walk into chorus — track:bass", "marcus"), ("feat(clavinet): filtered clavinet — muted pickstyle", "marcus"), ("fix(keys): fix missed chord on beat 4 bar 22", "marcus"), ("feat(drums): Mvt. IV — double-time feel — hi-hat 16th groove", "marcus"), ("feat(bass): fretless bass for Mvt. IV — floating groove", "marcus"), ], "jazz-trio": [ ("init: Bb major vamp — piano trio at 138 BPM", "marcus"), ("feat(piano): comping pattern — shell voicings 3-7", "marcus"), ("feat(bass): walking bass — Bb major standard changes", "marcus"), ("feat(drums): brushed snare pattern — triplet feel", "marcus"), ("feat(piano): solo chorus 1 — pentatonic approach", "marcus"), ("feat(bass): bass solo feature — rubato", "marcus"), ("feat(drums): trading 4s — kit break response", "marcus"), ("refactor(piano): reharmonize bridge — tritone subs", "marcus"), ("feat(piano): stride left hand in final chorus", "marcus"), ("fix(bass): fix intonation on F# — adjust finger placement", "marcus"), ("feat(drums): add brushed cymbal roll into solo sections", "marcus"), ("feat(piano): ballad tempo reduction for outro — ♩=72", "marcus"), ("refactor(bass): add counterpoint line during piano comping", "marcus"), ("fix(drums): remove extraneous kick note on bar 9", "marcus"), ("feat(piano): final cadenza — rubato", "marcus"), ], # Genre archive repos — batch-13 "wtc": [ ("init: Book I — Prelude No.1 in C major — arpeggiated harmony", "gabriel"), ("feat(fugue): Fugue No.1 in C major — 4-voice exposition", "gabriel"), ("feat(prelude): Prelude No.2 in C minor — perpetual motion 16ths", "sofia"), ("feat(fugue): Fugue No.2 in C minor — chromatic subject", "gabriel"), ("feat(prelude): Prelude No.3 in C# major — arpeggiated texture", "chen"), ("feat(fugue): Fugue No.3 in C# major — 3-voice stretto", "gabriel"), ("refactor(harmony): correct spelling of diminished 7th in bar 8", "pierre"), ("feat(prelude): Prelude No.4 in C# minor — lyrical melody", "gabriel"), ("feat(fugue): Fugue No.4 in C# minor — 5-voice exposition", "sofia"), ("feat(prelude): Prelude No.5 in D major — driving 16th notes", "gabriel"), ("feat(fugue): Fugue No.5 in D major — invertible counterpoint", "chen"), ("fix(voice-leading): resolve parallel 5ths in C major fugue bar 14", "gabriel"), ("feat(prelude): Prelude No.6 in D minor — expressive chromatics", "pierre"), ("feat(fugue): Fugue No.6 in D minor — augmentation in bass", "gabriel"), ("feat(prelude): Prelude No.7 in Eb major — ornate passagework", "sofia"), ("feat(fugue): Fugue No.7 in Eb major — inversion of subject", "gabriel"), ("refactor(ornamentation): add trills per Baroque convention — bars 1-4", "chen"), ("feat(prelude): Prelude No.8 in Eb minor — chromatic descent", "gabriel"), ("feat(fugue): Fugue No.8 in Eb minor — 3-voice with episode", "pierre"), ("feat(prelude): Prelude No.9 in E major — binary form", "gabriel"), ("feat(fugue): Fugue No.9 in E major — motivic development", "sofia"), ("fix(tuning): retune to equal temperament from well-temperament", "chen"), ("feat(prelude): Prelude No.10 in E minor — two-part invention style", "gabriel"), ("feat(fugue): Fugue No.10 in E minor — rhythmic diminution", "gabriel"), ("feat(book2): Book II — Prelude No.1 in C major — extended version", "sofia"), ("feat(book2): Fugue No.1 BK2 in C major — 4-voice with tonal answer", "gabriel"), ("feat(book2): Prelude No.2 BK2 in C minor — turbulent arpeggios", "pierre"), ("feat(book2): Fugue No.2 BK2 — chromatic subject, 4 voices", "gabriel"), ("refactor(dynamics): add hairpin dynamics per Urtext edition", "sofia"), ("feat(book2): Prelude No.3 BK2 in C# major — serene cantabile", "gabriel"), ], "goldberg": [ ("init: Goldberg Aria — sarabande in G major, ornate upper voice", "gabriel"), ("feat(var1): Variation 1 — two-voice in parallel 3rds", "gabriel"), ("feat(var2): Variation 2 — one voice per hand, canonic imitation", "sofia"), ("feat(var3): Variation 3 — canon at the unison", "gabriel"), ("feat(var4): Variation 4 — robust 4-voice passepied", "pierre"), ("feat(var5): Variation 5 — hand-crossing, one voice each hand", "gabriel"), ("feat(var6): Variation 6 — canon at the second", "sofia"), ("feat(var7): Variation 7 — gigue in 6/8, dance character", "gabriel"), ("feat(var8): Variation 8 — two-voice inversion in 3rds and 6ths", "chen"), ("feat(var13): Variation 13 — lyrical aria-like melody, experimental rubato", "gabriel"), ("fix(ornaments): correct trill resolution in Variation 13 bar 9", "sofia"), ("feat(var25): Variation 25 — chromatic aria, the emotional heart", "gabriel"), ("feat(var30): Variation 30 — Quodlibet, quotes folk songs", "gabriel"), ("feat(aria-reprise): Aria da capo — return of opening theme", "pierre"), ("refactor(voicing): ensure aria melody sits above all inner voices", "gabriel"), ("fix(voice-leading): remove parallel octaves in Variation 4 bar 12", "sofia"), ("refactor(tempo): apply consistent note values in 3/4 Variations", "gabriel"), ("feat(var21): Variation 21 — chromatic canon at the 7th", "chen"), ], "nocturnes": [ ("init: Op.9 No.1 in Bb minor — gentle arpeggiated bass, yearning melody", "aaliya"), ("feat(op9-2): Op.9 No.2 in Eb major — the iconic theme, ornate reprise", "aaliya"), ("feat(op9-3): Op.9 No.3 in B major — agitated middle section", "gabriel"), ("feat(op15-1): Op.15 No.1 in F major — pastoral melody, stormy development", "aaliya"), ("feat(op15-2): Op.15 No.2 in F# major — murmuring bass, cantabile melody", "sofia"), ("feat(op15-3): Op.15 No.3 in G minor — solemn choral opening", "aaliya"), ("feat(op27-1): Op.27 No.1 in C# minor — tragic opening, ecstatic climax", "gabriel"), ("feat(op27-2): Op.27 No.2 in Db major — sustained melody, ornate inner voice", "aaliya"), ("refactor(rubato): add tempo fluctuation markings per Chopin's own notation", "pierre"), ("fix(pedaling): correct sustain pedal placement in Op.9 No.2 bar 5", "aaliya"), ("feat(op32-1): Op.32 No.1 in B major — introspective, questioning end", "sofia"), ("feat(op32-2): Op.32 No.2 in Ab major — gentle but harmonically complex", "aaliya"), ("feat(op37-1): Op.37 No.1 in G minor — chorale-like, organistic", "gabriel"), ("feat(op37-2): Op.37 No.2 in G major — barcarolle-style 6/8", "aaliya"), ("refactor(ornamentation): add mordents and grace notes per autograph", "sofia"), ("fix(voice-leading): eliminate voice crossing in Op.15 No.3 bar 7", "aaliya"), ("feat(op48-1): Op.48 No.1 in C minor — grand and tragic", "gabriel"), ("feat(op48-2): Op.48 No.2 in F# minor — agitated and restless", "aaliya"), ("feat(op55-1): Op.55 No.1 in F minor — melancholic cantabile", "sofia"), ("feat(op55-2): Op.55 No.2 in Eb major — flowing, conversational", "aaliya"), ("feat(op62-1): Op.62 No.1 in B major — late style, fragmented ornament", "gabriel"), ("feat(op62-2): Op.62 No.2 in E major — tender farewell, inner voices", "aaliya"), ], "maple-leaf": [ ("init: Maple Leaf Rag in Ab major — 4/4 at 100 BPM", "marcus"), ("feat(A): Section A — syncopated melody over oom-pah bass, bars 1-16", "marcus"), ("feat(A-repeat): Section A repeat with octave doubling in melody", "gabriel"), ("feat(B): Section B — contrast, moves to Eb major", "marcus"), ("feat(B-repeat): Section B repeat — velocity humanized", "marcus"), ("feat(C): Section C (trio) — moves to Db major, more lyrical", "marcus"), ("feat(C-repeat): Section C repeat with improvised embellishment", "gabriel"), ("feat(D): Section D — returns to Ab, triumphant restatement", "marcus"), ("refactor(bass): tighten oom-pah bass timing — was 8ms ahead", "marcus"), ("fix(melody): correct grace note in bar 9 — was wrong pitch Eb not D", "gabriel"), ("feat(slow): slow-version — halftime feel, rubato allowed", "marcus"), ("feat(slow): slow-version extended ornaments in melody", "gabriel"), ("feat(edm): marcus-edm-remix — trap hi-hats under ragtime melody", "marcus"), ("feat(edm): marcus-edm-remix — 808 bass replacing oom-pah pattern", "marcus"), ], "cinematic-strings": [ ("init: Cinematic Strings in D minor — string orchestra at 64 BPM", "gabriel"), ("feat(intro): solo cello theme — bars 1-8 — pp, col arco", "chen"), ("feat(build): violas enter — pizzicato counter-rhythm, bars 9-16", "gabriel"), ("feat(build): second violins add sustained harmonic pad", "sofia"), ("feat(climax): full orchestra tutti — bars 33-40 — ff", "gabriel"), ("feat(climax): timpani and brass reinforcement at climax peak", "chen"), ("feat(resolution): strings return to solo cello — reprise of theme", "gabriel"), ("refactor(dynamics): smooth crescendo from pp to ff over 32 bars", "sofia"), ("fix(intonation): retune violin II section — was sharp by 5 cents", "gabriel"), ("feat(orchestral): orchestral branch — add oboe and clarinet doubling", "chen"), ("feat(orchestral): French horn countermelody in orchestral version", "gabriel"), ("feat(piano): stripped-piano branch — piano reduction of string score", "pierre"), ("feat(piano): add pedal markings to piano reduction", "sofia"), ("refactor(tempo): add ritardando at bar 38 for dramatic pause", "gabriel"), ("fix(articulation): add sul ponticello marking to Variation 2 strings", "chen"), ], "kai-ambient": [ ("init: Kai Engel ambient field — C major, slow morphing pad", "pierre"), ("feat(pad1): first layer — high strings, ppp, infinite sustain", "pierre"), ("feat(pad2): second pad — piano harmonics, prepared technique", "sofia"), ("feat(piano): sparse piano melody — whole notes, bars 9-24", "pierre"), ("feat(v1): v1 branch — original release version, 8-minute version", "pierre"), ("refactor(v1): v1 — master level adjusted to -14 LUFS", "sofia"), ("feat(v2): v2-extended — added 4-minute drone coda", "pierre"), ("feat(v2): v2-extended — new string layer in coda, sul tasto", "sofia"), ("fix(phase): reduce stereo width in pad2 to avoid phase cancellation", "pierre"), ("refactor(mix): filter low end on pad1 — HPF at 80Hz", "pierre"), ], "neo-baroque": [ ("init: Neo-Baroque in D Dorian — harpsichord + electric bass at 84 BPM", "gabriel"), ("feat(counterpoint): two-voice invention — right hand melody, left hand bass", "gabriel"), ("feat(jazz): jazz-voicings branch — quartal harmony replaces triads", "gabriel"), ("feat(jazz): tritone substitution in bar 8 turnaround — jazz-voicings", "marcus"), ("feat(jazz): rootless 9th voicings in right hand — jazz-voicings", "gabriel"), ("feat(harmony): harmonic sequence — descending 5ths in bass", "gabriel"), ("feat(rhythm): syncopated baroque rhythm — quarter-note displacement", "gabriel"), ("feat(edm): edm-bassline branch — 808 sub bass under baroque melody", "marcus"), ("feat(edm): four-on-the-floor kick added — edm-bassline", "gabriel"), ("feat(edm): filter sweep into bridge — edm-bassline", "marcus"), ("feat(harpsichord): feature/add-harpsichord — harpsichord replaces piano", "gabriel"), ("feat(harpsichord): double manual technique — feature/add-harpsichord", "pierre"), ("fix(voice-leading): parallel 5ths in bar 5 inner voices corrected", "gabriel"), ("refactor(form): add da capo repeat — bars 1-8 return at end", "gabriel"), ("feat(improv): jazz improvisation section — 8 bars over baroque changes", "marcus"), ("fix(timing): realign baroque ornaments to 16th grid", "gabriel"), ("feat(strings): add pizzicato baroque strings — bars 17-32", "sofia"), ("refactor(harmony): rewrite cadence — Phrygian half cadence, bar 16", "gabriel"), ("feat(coda): extended coda with fugal stretto — all voices", "gabriel"), ("fix(harpsichord): velocity normalization — harpsichord lacks dynamics", "gabriel"), ("feat(modal): modal interchange — borrow from D minor in bridge", "marcus"), ("refactor(mix): balance harpsichord vs bass — HF boost on harpsichord", "gabriel"), ("feat(ornament): mordent on beat 1 of each 4-bar phrase", "pierre"), ("feat(jazz2): jazz-voicings v2 — add upper-structure triads", "gabriel"), ("refactor(form): restructure to AABBA form — stronger contrast", "gabriel"), ("feat(bass): walking bass line for jazz-voicings bridge section", "marcus"), ("fix(modal): correct Dorian vs natural minor in bar 12", "gabriel"), ("feat(fugue): mini fugue in coda — 3-voice, 8 bars", "gabriel"), ], "jazz-chopin": [ ("init: Jazz Chopin — Op.9 No.2 reharmonized, Bb minor at 68 BPM", "aaliya"), ("feat(reharmonize): tritone sub on V7 chord — bar 4", "aaliya"), ("feat(reharmonize): minor ii-V-I substitution in bridge", "gabriel"), ("feat(voicing): rootless 9th chord voicings — left hand", "aaliya"), ("feat(reharmonize): Coltrane substitution pattern in climax — reharmonized", "aaliya"), ("feat(reharmonize): add chromatic approach chords — reharmonized", "marcus"), ("feat(trio): trio-arrangement — add bass and drums", "aaliya"), ("feat(trio): walking bass added under reharmonized changes", "marcus"), ("feat(trio): brushed snare — light jazz feel, trio-arrangement", "gabriel"), ("fix(voice-leading): parallel 5ths in reharmonized bridge, bar 9", "aaliya"), ("refactor(melody): add bebop ornaments to Chopin melody line", "aaliya"), ("feat(reharmonize): backdoor ii-V substitution in outro", "aaliya"), ("fix(bass): fix intonation issue on low Bb — walking bass", "marcus"), ("feat(trio): piano solo chorus over jazz changes — trio-arrangement", "aaliya"), ("refactor(tempo): add ritardando at 4-bar phrase ends", "aaliya"), ("feat(reharmonize): modal interchange — iv chord from parallel minor", "gabriel"), ("fix(drums): remove unintentional kick on beat 3 — trio", "aaliya"), ("feat(coda): free improvisation coda — all three voices", "aaliya"), ("refactor(harmony): ensure all substitutions maintain melodic identity", "aaliya"), ("feat(reharmonize): full reharmonized version complete — all 3 sections", "aaliya"), ], "ragtime-edm": [ ("init: Ragtime EDM — Maple Leaf Rag MIDI over trap beat at 128 BPM", "marcus"), ("feat(trap): trap hi-hat grid — 16th triplets with velocity variation", "marcus"), ("feat(trap): 808 kick on 1 and 3 — trap-version", "marcus"), ("feat(trap): snare on 2 and 4 with ghosted 16ths — trap-version", "gabriel"), ("feat(ragtime): ragtime melody quantized to EDM grid — bars 1-16", "marcus"), ("feat(house): house-version — 4-on-floor kick, Chicago-style", "marcus"), ("feat(house): sidechain compression on ragtime bass — house-version", "gabriel"), ("feat(house): filter sweep on ragtime melody — house-version", "marcus"), ("feat(swing): electro-swing branch — shuffle quantize 16ths to swing", "marcus"), ("feat(swing): brass sample layer on ragtime melody — electro-swing", "gabriel"), ("fix(pitch): transpose ragtime melody up one semitone to Ab for EDM mix", "marcus"), ("refactor(mix): sidechain bass to kick for pumping effect — all versions", "marcus"), ("feat(drop): big drop transition — silence then tutti return", "marcus"), ("fix(timing): tighten ragtime melody to EDM grid — was 10ms behind", "gabriel"), ("refactor(master): normalize to -8 LUFS for streaming platforms", "marcus"), ("feat(bridge): 8-bar bridge — minimal, just kick and ragtime melody fragment", "marcus"), ("feat(outro): outro — gradual filter close on all elements", "marcus"), ], "film-score": [ ("init: Film Score — Act I, C (31-TET) — establishing motif, tense, pp", "chen"), ("feat(act1): act1 — microtonal string cluster, bars 1-8", "chen"), ("feat(act1): act1 — ascending quarter-tone figure in winds", "chen"), ("feat(act1): act1 — timpani accent on beat 3 — instability motif", "gabriel"), ("feat(act1): act1 — brass pedal — low brass drone, bars 9-16", "chen"), ("feat(act2): act2 branch — spectral climax, full orchestra tutti", "chen"), ("feat(act2): act2 — strings in high register, ff, sul ponticello", "sofia"), ("feat(act2): act2 — timpani rolls and brass fanfare — climax peak", "chen"), ("feat(act2): act2 — dissonant chord cluster, all 31-TET pitches", "gabriel"), ("feat(act3): act3 branch — resolution, return to simple C major", "chen"), ("feat(act3): act3 — solo violin melody, simple diatonic, pp", "sofia"), ("feat(act3): act3 — gradual orchestral return from silence", "chen"), ("feat(act3): act3 — final chord — C major, fff, held for 8 bars", "gabriel"), ("fix(tuning): recalibrate all instruments to 31-TET in Act I", "chen"), ("refactor(dynamics): smooth transition from Act I pp to Act II ff", "chen"), ("fix(voice-leading): remove dissonance clash in Act III resolution", "sofia"), ("refactor(score): add rehearsal letter marks every 8 bars", "chen"), ("feat(leitmotif): recurring motif appears in each act — unifying thread", "gabriel"), ], "polyrhythm": [ ("init: Polyrhythm Studies — A minor at 92 BPM — 7-over-4 base", "fatou"), ("feat(7-4): 7-over-4 — djembe in 7, talking drum in 4", "fatou"), ("feat(7-4): 7-over-4 — bass guitar anchors common pulse", "fatou"), ("feat(7-4): 7-over-4 — listener orientation — hi-hat on beat 1 only", "aaliya"), ("refactor(7-4): humanize djembe timing — ±8ms variance", "fatou"), ("feat(5-3): 5-over-3-experiment — conga in 5, shekere in 3", "fatou"), ("feat(5-3): 5-over-3-experiment — bass anchors on shared beat", "aaliya"), ("feat(5-3): 5-over-3-experiment — add melody on shared downbeats only", "fatou"), ("fix(phase): fix drifting phase in 7-over-4 at bar 32 — MIDI timing", "fatou"), ("feat(groove): add cross-stick snare to bridge the two rhythmic worlds", "aaliya"), ("refactor(mix): bring up djembe attack — was buried under bass", "fatou"), ], "community": [ ("init: Community Collab — open canvas — C major, 90 BPM", "gabriel"), ("feat(counterpoint): sofia's counterpoint — Bach-inspired 2-voice invention", "sofia"), ("feat(ornament): yuki's ornaments — granular delay on all voices", "yuki"), ("feat(analysis): pierre's analysis annotations — harmonic function labels", "pierre"), ("feat(bass): marcus's bassline — funk groove under baroque counterpoint", "marcus"), ("feat(rhythm): fatou's polyrhythm layer — 5-over-3 pattern over 4/4", "fatou"), ("feat(reharmonize): aaliya's jazz reharmonization of C major progression", "aaliya"), ("feat(microtonal): chen's microtonal ornaments — quarter-tone glissandi", "chen"), ("refactor(structure): gabriel rebalances all layers — new mix", "gabriel"), ("feat(baroque): sofia adds fugal episode — subject and answer", "sofia"), ("feat(texture): yuki adds granular texture layer — sparse grain scatter", "yuki"), ("fix(voice-leading): pierre fixes parallel 5ths — bars 12-13", "pierre"), ("feat(groove): marcus adds clavinet stabs — funk energy", "marcus"), ("feat(perc): fatou adds shekere pulse — holds everything together", "fatou"), ("feat(jazz): aaliya adds blue notes to melody — jazzy feel", "aaliya"), ("feat(tuning): chen corrects micro-tuning in ornament layer", "chen"), ("feat(improv): gabriel improvises bridge over new changes", "gabriel"), ("feat(strings): sofia adds lush string pad — bars 33-48", "sofia"), ("feat(reverb): yuki adds cathedral reverb to string layer", "yuki"), ("feat(harmony): pierre adds 9th and 11th extensions to all chords", "pierre"), ("feat(bass2): marcus doubles bassline at octave — thicker low end", "marcus"), ("feat(perc2): fatou adds djembe solo — bars 49-56", "fatou"), ("feat(modal): aaliya introduces Dorian mode shift in bridge", "aaliya"), ("feat(spectral): chen adds spectral filter sweep — act of transformation", "chen"), ("feat(motif): gabriel introduces 4-note motif — appears in all layers", "gabriel"), ("refactor(mix): sofia adjusts balance — counterpoint more prominent", "sofia"), ("feat(scatter): yuki reduces grain density for introspective section", "yuki"), ("feat(chorale): pierre writes 4-voice chorale climax — bars 57-64", "pierre"), ("feat(solo): marcus piano solo over baroque changes", "marcus"), ("feat(perc3): fatou polyrhythm climax — all layers simultaneously", "fatou"), ("feat(reprise): aaliya leads reprise of opening theme — reharmonized", "aaliya"), ("feat(finale): chen's finale motif — microtonal glissando into last chord", "chen"), ("feat(coda): gabriel's coda — reduces to solo piano, pp", "gabriel"), ("refactor(final-mix): sofia final mix pass — all dynamics balanced", "sofia"), ("feat(outro): yuki granular outro — voices dissolve into texture", "yuki"), ("feat(credits): pierre adds annotation — credits all contributors", "pierre"), ], } key = repo_key templates = TEMPLATES.get(key, TEMPLATES["neo-soul"]) commits: list[dict[str, Any]] = [] prev_id: str | None = None branch = "main" t = templates * ((n // len(templates)) + 1) for i in range(n): cid = _sha(f"{repo_id}-commit-{i}") msg = t[i % len(templates)][0] author = t[i % len(templates)][1] days = (n - i) * 2 # older commits further back commits.append(dict( commit_id=cid, repo_id=repo_id, branch=branch, parent_ids=[prev_id] if prev_id else [], message=msg, author=author, timestamp=_now(days=days), snapshot_id=_sha(f"snap-{repo_id}-{i}"), )) prev_id = cid # Sprinkle in a feature branch every ~8 commits if i > 0 and i % 8 == 0: branch = "main" return commits # Track roles per repo key for the instrument breakdown bar REPO_TRACKS: dict[str, list[tuple[str, str]]] = { "neo-soul": [("bass", "tracks/bass.mid"), ("keys", "tracks/rhodes.mid"), ("drums", "tracks/drums.mid"), ("strings", "tracks/strings.mid"), ("horns", "tracks/trumpet.mid"), ("horns", "tracks/alto_sax.mid"), ("guitar", "tracks/guitar.mid"), ("vocals", "tracks/vocals.mid")], "modal-jazz": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"), ("drums", "tracks/drums.mid"), ("trumpet", "tracks/trumpet.mid"), ("guitar", "tracks/guitar.mid")], "ambient": [("pad", "tracks/pad.mid"), ("arp", "tracks/arpeggiator.mid"), ("strings", "tracks/strings.mid"), ("bells", "tracks/bells.mid"), ("drone", "tracks/drone.mid")], "afrobeat": [("perc", "tracks/talking_drum.mid"), ("guitar", "tracks/guitar.mid"), ("bass", "tracks/bass.mid"), ("horns", "tracks/horns.mid"), ("keys", "tracks/rhodes.mid"), ("perc", "tracks/shekere.mid"), ("vocals", "tracks/vocals.mid")], "microtonal": [("piano", "tracks/piano.mid"), ("strings", "tracks/strings.mid"), ("woodwinds", "tracks/woodwinds.mid"), ("perc", "tracks/percussion.mid")], "drums": [("kick", "tracks/kick.mid"), ("snare", "tracks/snare.mid"), ("hihat", "tracks/hihat.mid"), ("perc", "tracks/djembe.mid"), ("808", "tracks/808.mid")], "chanson": [("piano", "tracks/piano.mid"), ("cello", "tracks/cello.mid")], "granular": [("pad", "tracks/granular_pad.mid"), ("texture", "tracks/texture.mid"), ("rhythm", "tracks/rhythmic.mid")], "funk-suite": [("bass", "tracks/bass.mid"), ("keys", "tracks/electric_piano.mid"), ("clavinet", "tracks/clavinet.mid"), ("drums", "tracks/drums.mid"), ("guitar", "tracks/guitar.mid"), ("horns", "tracks/horns.mid"), ("perc", "tracks/congas.mid")], "jazz-trio": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"), ("drums", "tracks/drums.mid")], # Genre archive repos — batch-13 "wtc": [("piano", "tracks/piano.mid"), ("harpsichord", "tracks/harpsichord.mid")], "goldberg": [("piano", "tracks/piano.mid"), ("harpsichord", "tracks/harpsichord.mid")], "nocturnes": [("piano", "tracks/piano.mid")], "maple-leaf": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid")], "cinematic-strings":[("violin1", "tracks/violin1.mid"), ("violin2", "tracks/violin2.mid"), ("viola", "tracks/viola.mid"), ("cello", "tracks/cello.mid"), ("bass", "tracks/double_bass.mid"), ("timp", "tracks/timpani.mid")], "kai-ambient": [("pad", "tracks/pad.mid"), ("piano", "tracks/piano.mid"), ("strings", "tracks/strings.mid")], "neo-baroque": [("harpsichord", "tracks/harpsichord.mid"), ("bass", "tracks/bass.mid"), ("strings", "tracks/strings.mid")], "jazz-chopin": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"), ("drums", "tracks/drums.mid")], "ragtime-edm": [("piano", "tracks/piano.mid"), ("kick", "tracks/kick.mid"), ("snare", "tracks/snare.mid"), ("hihat", "tracks/hihat.mid"), ("808", "tracks/808.mid")], "film-score": [("strings", "tracks/strings.mid"), ("brass", "tracks/brass.mid"), ("woodwinds", "tracks/woodwinds.mid"), ("timp", "tracks/timpani.mid")], "polyrhythm": [("djembe", "tracks/djembe.mid"), ("tama", "tracks/talking_drum.mid"), ("shekere", "tracks/shekere.mid"), ("bass", "tracks/bass.mid")], "community": [("piano", "tracks/piano.mid"), ("bass", "tracks/bass.mid"), ("strings", "tracks/strings.mid"), ("perc", "tracks/djembe.mid"), ("harpsichord", "tracks/harpsichord.mid"), ("pad", "tracks/granular_pad.mid")], } REPO_KEY_MAP = { REPO_NEO_SOUL: "neo-soul", REPO_MODAL_JAZZ: "modal-jazz", REPO_AMBIENT: "ambient", REPO_AFROBEAT: "afrobeat", REPO_MICROTONAL: "microtonal", REPO_DRUM_MACHINE: "drums", REPO_CHANSON: "chanson", REPO_GRANULAR: "granular", REPO_FUNK_SUITE: "funk-suite", REPO_JAZZ_TRIO: "jazz-trio", REPO_NEO_SOUL_FORK: "neo-soul", REPO_AMBIENT_FORK: "ambient", # Genre archive repos — batch-13 REPO_WTC: "wtc", REPO_GOLDBERG: "goldberg", REPO_NOCTURNES: "nocturnes", REPO_MAPLE_LEAF: "maple-leaf", REPO_CIN_STRINGS: "cinematic-strings", REPO_KAI_AMBIENT: "kai-ambient", REPO_NEO_BAROQUE: "neo-baroque", REPO_JAZZ_CHOPIN: "jazz-chopin", REPO_RAGTIME_EDM: "ragtime-edm", REPO_FILM_SCORE: "film-score", REPO_POLYRHYTHM: "polyrhythm", REPO_COMMUNITY: "community", } COMMIT_COUNTS = { REPO_NEO_SOUL: 40, REPO_MODAL_JAZZ: 30, REPO_AMBIENT: 35, REPO_AFROBEAT: 38, REPO_MICROTONAL: 25, REPO_DRUM_MACHINE: 28, REPO_CHANSON: 22, REPO_GRANULAR: 24, REPO_FUNK_SUITE: 42, REPO_JAZZ_TRIO: 32, REPO_NEO_SOUL_FORK: 8, REPO_AMBIENT_FORK: 5, # Genre archive repos — batch-13 REPO_WTC: 60, REPO_GOLDBERG: 35, REPO_NOCTURNES: 45, REPO_MAPLE_LEAF: 25, REPO_CIN_STRINGS: 30, REPO_KAI_AMBIENT: 20, REPO_NEO_BAROQUE: 55, REPO_JAZZ_CHOPIN: 40, REPO_RAGTIME_EDM: 35, REPO_FILM_SCORE: 28, REPO_POLYRHYTHM: 22, REPO_COMMUNITY: 70, } # Specific branch configurations for genre archive repos (batch-13). # Each entry: list of (branch_name, commit_offset_from_end) — offset 0 = HEAD. GENRE_REPO_BRANCHES: dict[str, list[tuple[str, int]]] = { REPO_WTC: [("prelude-bk1", 50), ("fugue-bk1", 42), ("prelude-bk2", 20), ("fugue-bk2", 10)], REPO_GOLDBERG: [("aria-only", 30), ("variation-13-experimental", 15)], REPO_NOCTURNES: [("op9", 38), ("op15", 25), ("op27", 12)], REPO_MAPLE_LEAF: [("slow-version", 10), ("marcus-edm-remix", 5)], REPO_CIN_STRINGS: [("orchestral", 20), ("stripped-piano", 8)], REPO_KAI_AMBIENT: [("v1", 14), ("v2-extended", 6)], REPO_NEO_BAROQUE: [("experiment/jazz-voicings", 45), ("experiment/edm-bassline", 30), ("feature/add-harpsichord", 15)], REPO_JAZZ_CHOPIN: [("reharmonized", 30), ("trio-arrangement", 15)], REPO_RAGTIME_EDM: [("trap-version", 28), ("house-version", 18), ("electro-swing", 8)], REPO_FILM_SCORE: [("act1", 22), ("act2", 14), ("act3", 6)], REPO_POLYRHYTHM: [("7-over-4", 16), ("5-over-3-experiment", 8)], REPO_COMMUNITY: [("sofias-counterpoint", 60), ("yukis-ornaments", 50), ("pierres-analysis", 40), ("marcuss-bassline", 25)], } # --------------------------------------------------------------------------- # Muse VCS — content-addressed MIDI objects, snapshots, commits, tags # --------------------------------------------------------------------------- # Track files per repo for Muse VCS — realistic MIDI instrument names and sizes. # Piano solo: 8KB–40KB; ensemble: 50KB–200KB (task spec). # Each tuple is (filename, base_size_bytes). MUSE_VCS_FILES: dict[str, list[tuple[str, int]]] = { REPO_NEO_SOUL: [("piano.mid", 24576), ("bass.mid", 12288), ("drums.mid", 16384), ("violin.mid", 18432), ("trumpet.mid", 13312)], REPO_FUNK_SUITE: [("piano.mid", 22528), ("bass.mid", 13312), ("drums.mid", 16384), ("trumpet.mid", 12288), ("flute.mid", 10240)], REPO_AFROBEAT: [("bass.mid", 14336), ("drums.mid", 18432), ("violin.mid", 15360), ("cello.mid", 14336), ("trumpet.mid", 12288)], REPO_AMBIENT: [("piano.mid", 32768), ("violin.mid", 20480), ("cello.mid", 17408), ("viola.mid", 15360), ("flute.mid", 11264)], REPO_MODAL_JAZZ: [("piano.mid", 28672), ("bass.mid", 10240), ("drums.mid", 14336), ("trumpet.mid", 11264)], REPO_JAZZ_TRIO: [("piano.mid", 26624), ("bass.mid", 11264), ("drums.mid", 13312)], REPO_MICROTONAL: [("piano.mid", 20480), ("violin.mid", 16384), ("cello.mid", 14336)], REPO_DRUM_MACHINE: [("drums.mid", 18432), ("bass.mid", 12288)], REPO_CHANSON: [("piano.mid", 36864), ("cello.mid", 17408)], REPO_GRANULAR: [("piano.mid", 15360), ("violin.mid", 12288), ("flute.mid", 9216)], REPO_NEO_SOUL_FORK:[("piano.mid", 24576), ("bass.mid", 12288), ("drums.mid", 16384)], REPO_AMBIENT_FORK: [("piano.mid", 32768), ("violin.mid", 20480), ("cello.mid", 17408)], } # Metadata per repo for muse_commits.metadata JSON field. MUSE_COMMIT_META: dict[str, dict[str, object]] = { REPO_NEO_SOUL: {"tempo_bpm": 92.0, "key": "F# minor", "time_signature": "4/4", "instrument_count": 5}, REPO_FUNK_SUITE: {"tempo_bpm": 108.0, "key": "E minor", "time_signature": "4/4", "instrument_count": 5}, REPO_AFROBEAT: {"tempo_bpm": 128.0, "key": "G major", "time_signature": "12/8","instrument_count": 5}, REPO_AMBIENT: {"tempo_bpm": 60.0, "key": "Eb major", "time_signature": "4/4", "instrument_count": 5}, REPO_MODAL_JAZZ: {"tempo_bpm": 120.0, "key": "D Dorian", "time_signature": "4/4", "instrument_count": 4}, REPO_JAZZ_TRIO: {"tempo_bpm": 138.0, "key": "Bb major", "time_signature": "3/4", "instrument_count": 3}, REPO_MICROTONAL: {"tempo_bpm": 76.0, "key": "C (31-TET)","time_signature":"4/4", "instrument_count": 3}, REPO_DRUM_MACHINE: {"tempo_bpm": 100.0, "key": "A minor", "time_signature": "4/4", "instrument_count": 2}, REPO_CHANSON: {"tempo_bpm": 52.0, "key": "A major", "time_signature": "4/4", "instrument_count": 2}, REPO_GRANULAR: {"tempo_bpm": 70.0, "key": "E minor", "time_signature": "4/4", "instrument_count": 3}, REPO_NEO_SOUL_FORK:{"tempo_bpm": 92.0, "key": "F# minor", "time_signature": "4/4", "instrument_count": 3}, REPO_AMBIENT_FORK: {"tempo_bpm": 60.0, "key": "Eb major", "time_signature": "4/4", "instrument_count": 3}, } # Muse tag taxonomy — ALL values from the task spec must appear in the seed. MUSE_EMOTION_TAGS = [ "melancholic", "joyful", "tense", "serene", "triumphant", "mysterious", "playful", "tender", "energetic", "complex", ] MUSE_STAGE_TAGS = [ "sketch", "rough-mix", "arrangement", "production", "mixing", "mastering", "released", ] MUSE_KEY_TAGS = [ "C", "Am", "G", "Em", "Bb", "F#", "Db", "Abm", "D", "Bm", "A", "F", "Eb", "Cm", ] MUSE_TEMPO_TAGS = [ "60bpm", "72bpm", "80bpm", "96bpm", "120bpm", "132bpm", "140bpm", "160bpm", ] MUSE_GENRE_TAGS = [ "baroque", "romantic", "ragtime", "edm", "ambient", "cinematic", "jazz", "afrobeats", "classical", "fusion", ] MUSE_REF_TAGS = [ "bach", "chopin", "debussy", "coltrane", "daft-punk", "beethoven", "joplin", "monk", ] # Full flat list of all taxonomy tags — used when cycling through commits. _ALL_MUSE_TAGS: list[str] = ( MUSE_EMOTION_TAGS + MUSE_STAGE_TAGS + MUSE_KEY_TAGS + MUSE_TEMPO_TAGS + MUSE_GENRE_TAGS + MUSE_REF_TAGS ) # Repos that get the full rich tag taxonomy (most active, richest history). MUSE_RICH_TAG_REPOS = {REPO_NEO_SOUL, REPO_FUNK_SUITE} # --------------------------------------------------------------------------- # Muse variation history — DAW project constants # --------------------------------------------------------------------------- # Two most active DAW projects used for variation seeding. # project_id values are deterministic UUIDs so they survive re-seeds. PROJECT_NEO_BAROQUE = _uid("project-gabriel-neo-baroque") PROJECT_COMMUNITY_COLLAB = _uid("project-gabriel-community-collab") PHRASE_TYPES = ["melody", "harmony", "bass", "rhythm", "pad", "lead"] VARIATION_INTENTS_NEO_BAROQUE = [ "Add counterpoint line above the baroque theme in bars 9-16", "Reharmonize the continuo with IV-V-I instead of I-IV-V", "Double the melody at the upper octave in the A section", "Reduce note density in the ornament layer — too busy", "Add a fermata on the penultimate chord for dramatic pause", "Transpose the inner voice down a third for smoother voice-leading", "Replace the parallel motion with contrary motion in bars 5-8", "Add suspensions (4-3, 7-6) on the strong beats of the progression", "Introduce a sequence pattern (descending thirds) in the episode", "Thicken the bass line with octave doubling", "Add a ritardando in the final four bars", "Experiment with a Neapolitan chord before the final cadence", "Restructure the ornamentation — trills only on structural beats", "Add an inner pedal point on the dominant during the development", "Rewrite the melodic leap (octave) as stepwise with passing tones", "Apply tierce de Picardie on the final chord", "Compress the sequence pattern to fit 2-bar phrases", "Add imitation between soprano and bass at the interval of a 4th", "Slow harmonic rhythm in the development section", "Introduce a chromatic passing tone in bar 12 inner voice", ] VARIATION_INTENTS_COMMUNITY_COLLAB = [ "Blend the neo-soul groove with the afrobeat polyrhythm layer", "Merge gabriel's chord voicings with aaliya's bass pattern", "Add marcus's funk stabs over the ambient pad foundation", "Cross-fade between yuki's granular texture and sofia's arp", "Combine pierre's chanson melody with the modal jazz harmony", "Overlay chen's microtonal texture on the funk groove", "Mix fatou's djembe pattern with the electronic kick drum", "Harmonise gabriel's melody with aaliya's Yoruba vocal line", "Layer marcus's Rhodes over the afrobeat rhythm section", "Merge the granular scatter with the orchestral strings", "Add a call-and-response between the jazz trio and the afrobeat horns", "Blend microtonal pitch-bends with the neo-soul Rhodes voicing", "Cross-fade the ambient pad into the funk breakdown", "Combine pierre's cello with fatou's 808 bass for a hybrid outro", "Layer gabriel's polyrhythm with yuki's rhythmic granular engine", "Blend chen's otonal hexad with sofia's generative arpeggio", "Mix aaliya's talking drum with marcus's brushed snare", "Overlay the modal jazz walking bass under the afrobeat groove", "Merge the Chanson ostinato with the funk electric piano stabs", "Cross-fade the microtonal étude into the neo-baroque continuo", ] TRACK_IDS_NEO_BAROQUE = [ _uid("track-nb-soprano"), _uid("track-nb-alto"), _uid("track-nb-tenor"), _uid("track-nb-bass"), _uid("track-nb-continuo"), _uid("track-nb-violin"), ] TRACK_IDS_COMMUNITY = [ _uid("track-cc-lead"), _uid("track-cc-harmony"), _uid("track-cc-bass"), _uid("track-cc-drums"), _uid("track-cc-pad"), _uid("track-cc-horns"), ] REGION_IDS_NEO_BAROQUE = [_uid(f"region-nb-{i}") for i in range(8)] REGION_IDS_COMMUNITY = [_uid(f"region-cc-{i}") for i in range(8)] def _make_note_dict( pitch: int, velocity: int, start_beat: float, duration_beats: float, track_id: str, region_id: str, ) -> NoteDict: """Build a NoteDict payload for before_json / after_json.""" return NoteDict( pitch=pitch, velocity=velocity, start_beat=start_beat, duration_beats=duration_beats, track_id=track_id, region_id=region_id, ) def _make_variation_section( project_id: str, intents: list[str], track_ids: list[str], region_ids: list[str], base_commit_hashes: list[str], seed_prefix: str, ) -> tuple[list[Variation], list[Phrase], list[NoteChange]]: """Generate 30 variations (20 accepted, 5 discarded, 5 pending) with realistic phrase and note-change children for a single DAW project. Parent chains (draft → refined → final) are formed in groups of 3-5. Three variations are merge variations with parent2_variation_id set. """ variations: list[Variation] = [] phrases: list[Phrase] = [] note_changes: list[NoteChange] = [] # Build 30 variations. Status distribution: # [0..19] accepted, [20..24] discarded, [25..29] pending STATUS_MAP = ( ["accepted"] * 20 + ["discarded"] * 5 + ["pending"] * 5 ) var_ids: list[str] = [ _uid(f"{seed_prefix}-var-{i}") for i in range(30) ] # Form parent chains in groups: [0-3], [4-7], [8-11], [12-15], [16-19], # [20-22], [23-24], [25-27], [28-29] chain_groups = [ [0, 1, 2, 3], # accepted chain of 4 [4, 5, 6, 7], # accepted chain of 4 [8, 9, 10, 11], # accepted chain of 4 [12, 13, 14], # accepted chain of 3 [15, 16, 17, 18, 19], # accepted chain of 5 [20, 21, 22], # discarded chain of 3 [23, 24], # discarded chain of 2 [25, 26, 27], # pending chain of 3 [28, 29], # pending chain of 2 ] # Merge variations at indices 7, 11, 15 — they get parent2_variation_id. # All parent2 references point BACKWARD (earlier in the sequence) so the # entire batch can be flushed in one call without FK violations. merge_indices = {7, 11, 15} merge_parent2_map = { 7: var_ids[0], # end of chain-2 merges with start of chain-1 11: var_ids[3], # end of chain-3 merges with end of chain-1 15: var_ids[7], # start of chain-5 merges with end of chain-2 } # Build parent_variation_id mapping parent_map: dict[int, str | None] = {} for chain in chain_groups: for pos, idx in enumerate(chain): parent_map[idx] = var_ids[chain[pos - 1]] if pos > 0 else None now = _now() for i in range(30): status = STATUS_MAP[i] intent = intents[i % len(intents)] base_hash = base_commit_hashes[i % len(base_commit_hashes)] # muse_variations.base_state_id / commit_state_id are VARCHAR(36) — derive # a UUID from the 64-char SHA-256 commit hash so it fits the column. base_state_uuid = _uid(base_hash) parent_vid = parent_map.get(i) parent2_vid = merge_parent2_map.get(i) if i in merge_indices else None is_head = status == "accepted" and i == 19 var = Variation( variation_id=var_ids[i], project_id=project_id, base_state_id=base_state_uuid, conversation_id=_uid(f"{seed_prefix}-conv-{i // 5}"), intent=intent, explanation=f"Variation {i+1}: {intent[:60]}", status=status, affected_tracks=[track_ids[i % len(track_ids)]], affected_regions=[region_ids[i % len(region_ids)]], beat_range_start=float((i % 8) * 8), beat_range_end=float((i % 8) * 8 + 16), parent_variation_id=parent_vid, parent2_variation_id=parent2_vid, commit_state_id=base_state_uuid if status == "accepted" else None, is_head=is_head, created_at=_now(days=30 - i), updated_at=_now(days=30 - i), ) variations.append(var) # 2-5 phrases per variation phrase_count = 2 + (i % 4) for p in range(phrase_count): start_beat = float(((i % 8) * 8 + p * 4) % 64) end_beat = start_beat + 4.0 + float((p % 3) * 4) phrase_type = PHRASE_TYPES[p % len(PHRASE_TYPES)] tid = track_ids[(i + p) % len(track_ids)] rid = region_ids[(i + p) % len(region_ids)] # CC events attached to phrases with sustain/expression/modulation/volume cc_data = [ {"cc": 64, "beat": start_beat + 0.5, "value": 127}, {"cc": 11, "beat": start_beat + 1.0, "value": 90}, ] if p % 2 == 0 else [ {"cc": 1, "beat": start_beat + 0.5, "value": 50}, {"cc": 7, "beat": start_beat + 1.0, "value": 100}, ] # Pitch bend on every third phrase pitch_bends_data = ( [{"beat": start_beat + 2.5, "value": 4096}] if p % 3 == 0 else None ) phrase = Phrase( phrase_id=_uid(f"{seed_prefix}-phrase-{i}-{p}"), variation_id=var_ids[i], sequence=p, track_id=tid, region_id=rid, start_beat=start_beat, end_beat=end_beat, label=phrase_type, tags=[phrase_type, "seed"], explanation=f"Phrase {p+1} ({phrase_type}) of variation {i+1}", cc_events=cc_data, pitch_bends=pitch_bends_data, aftertouch=None, region_start_beat=start_beat, region_duration_beats=end_beat - start_beat, region_name=f"Region-{rid[:8]}", ) phrases.append(phrase) # 4-20 note changes per phrase note_count = 4 + ((i * 3 + p * 7) % 17) for n in range(note_count): pitch_base = 48 + (n * 4) % 60 # MIDI 48-108 vel = 30 + (n * 7) % 98 # velocity 30-127 nb = start_beat + float(n) * 0.5 dur = 0.25 + float(n % 4) * 0.25 + float((n // 4) % 4) * 0.5 # Cycle through change types — canonical values from contracts/json_types.py if n % 3 == 0: change_type = "added" before_j = None after_j = _make_note_dict(pitch_base, vel, nb, dur, tid, rid) elif n % 3 == 1: change_type = "removed" before_j = _make_note_dict(pitch_base, vel, nb, dur, tid, rid) after_j = None else: change_type = "modified" orig_pitch = pitch_base - 2 orig_vel = max(30, vel - 12) orig_beat = nb - 0.25 before_j = _make_note_dict(orig_pitch, orig_vel, orig_beat, dur, tid, rid) after_j = _make_note_dict(pitch_base, vel, nb, dur, tid, rid) nc = NoteChange( id=_uid(f"{seed_prefix}-nc-{i}-{p}-{n}"), phrase_id=_uid(f"{seed_prefix}-phrase-{i}-{p}"), change_type=change_type, before_json=before_j, after_json=after_j, ) note_changes.append(nc) return variations, phrases, note_changes # --------------------------------------------------------------------------- # Issue templates # --------------------------------------------------------------------------- ISSUE_TEMPLATES: dict[str, list[dict[str, Any]]] = { "neo-soul": [ dict(n=1, state="open", title="Bass line loses tension in bar 9", body="3-against-4 pulse drifts. Ghost note on beat 2.5 recommended.", labels=["groove", "bass"]), dict(n=2, state="open", title="Add guitar scratch rhythm track", body="Arrangement too sparse. Scratch guitar would complement Rhodes.", labels=["arrangement"]), dict(n=3, state="closed", title="Tempo fluctuates bars 4-8", body="Resolved by re-quantizing with tight humanization.", labels=["tempo", "drums"]), dict(n=4, state="open", title="Choir voicing too wide in chorus", body="Soprano and bass parts are 2+ octaves apart — muddy on small speakers.", labels=["harmony"]), dict(n=5, state="open", title="Organ swell clashes with Rhodes", body="Both sit in mid-range 400-800Hz. Pan or EQ to separate.", labels=["mix"]), dict(n=6, state="closed", title="String pizzicato timing off", body="Fixed — re-quantized to 16th grid with 10ms humanize.", labels=["strings", "timing"]), dict(n=7, state="open", title="Bridge needs more harmonic tension", body="The IV-I cadence in the bridge is too resolved. Try IV-bVII.", labels=["harmony", "bridge"]), dict(n=8, state="open", title="Trumpet counter-melody too high", body="Goes above high C. Alto sax range would be more idiomatic.", labels=["horns"]), dict(n=9, state="closed", title="Bass note collision on beat 1", body="Fixed — root changed from F# to C# (5th) to reduce mud.", labels=["bass", "harmony"]), dict(n=10, state="open", title="Add breakdown section before final chorus", body="Energy needs to drop before the big finish. 4-bar bass+drums only.", labels=["arrangement"]), dict(n=11, state="open", title="Vocals too bright — needs de-essing", body="Sibilance prominent on headphones. High shelf cut above 10kHz.", labels=["mix", "vocals"]), dict(n=12, state="open", title="Consider key change to A minor for outro", body="A modulation to relative major would give a brighter feel at the end.", labels=["harmony"]), dict(n=13, state="closed", title="Rhodes voicing clashes in bar 12", body="Fixed — upper structure triad replaced with shell voicing (root + 7th).", labels=["piano", "harmony"]), dict(n=14, state="open", title="Add shaker for groove density in pre-chorus", body="The pre-chorus feels lighter than the verse. A 16th-note shaker would tie the pulse together.", labels=["groove", "perc"]), dict(n=15, state="open", title="Vocal compression artifacts on sustained notes", body="Long vowels show pumping at attack. Reduce ratio from 8:1 to 4:1 and increase attack to 10ms.", labels=["mix", "vocals"]), ], "modal-jazz": [ dict(n=1, state="open", title="Phrygian bridge needs ii-V turnaround", body="Jump from D Dorian to E Phrygian is abrupt. Add Am7b5 → D7alt.", labels=["harmony"]), dict(n=2, state="open", title="Swing factor inconsistent piano vs bass", body="Piano at 0.65 swing, bass at 0.55. Should match.", labels=["groove", "timing"]), dict(n=3, state="closed", title="Piano pedaling too heavy in changes", body="Fixed — reduced sustain pedal range.", labels=["piano"]), dict(n=4, state="open", title="Guitar chord stabs too loud", body="Freddie Green stabs should sit under the piano. Lower -3dB.", labels=["mix", "guitar"]), dict(n=5, state="open", title="Head melody needs resolution note", body="The A section ends on 6th scale degree — unresolved. Add scale degree 1.", labels=["melody"]), dict(n=6, state="open", title="Tritone sub reharmonization too frequent", body="Using sub every 2 bars sounds formulaic. Reserve for 8-bar phrase end.", labels=["harmony"]), dict(n=7, state="closed", title="Bass solo too long — loses listener", body="Trimmed to 16 bars. Better pacing.", labels=["bass"]), dict(n=8, state="open", title="Drummer needs to lay back on trumpet solo", body="Ride accent too prominent during solo. Comp more sparsely.", labels=["drums"]), dict(n=9, state="open", title="Piano comping too busy in A section", body="Left-hand comp obscures walking bass line. Simplify to 2-feel.", labels=["piano", "arrangement"]), dict(n=10, state="closed", title="Trumpet range error — written vs concert pitch", body="Fixed — all trumpet parts transposed down a major 2nd to concert pitch.", labels=["horns"]), dict(n=11, state="open", title="Add lydian mode variation in B section", body="The B section stays strictly Dorian. A Lydian passage would add color.", labels=["harmony"]), dict(n=12, state="open", title="Bass register too low in chorus", body="Walking bass drops below E1 — inaudible on most systems. Transpose up an octave.", labels=["bass"]), dict(n=13, state="open", title="Snare ghost notes need velocity curve", body="All ghosts at velocity 40 — too uniform. Use 20-50 range with slight randomization.", labels=["drums"]), dict(n=14, state="closed", title="Key center ambiguous in intro", body="Fixed — added a clear D Dorian vamp at the start before the head.", labels=["harmony"]), dict(n=15, state="open", title="Outro needs ritardando", body="The piece ends abruptly at tempo. Gradual slow-down over last 4 bars would give closure.", labels=["arrangement"]), ], "ambient": [ dict(n=1, state="open", title="Arpeggiator repeats — needs more variation", body="After 32 bars the pattern becomes predictable. Modulate seed every 8 bars.", labels=["generative"]), dict(n=2, state="open", title="Pad too washy — needs more definition", body="Attack of 4s is too slow. Try 2s with a short sustain plateau.", labels=["pad"]), dict(n=3, state="closed", title="Stuck note in arp at bar 64", body="Fixed — MIDI note-off added. Was a gate issue.", labels=["bug", "midi"]), dict(n=4, state="open", title="Add harmonic movement after bar 48", body="The Eb pedal has been static for 3 minutes. Move to Ab for 8 bars.", labels=["harmony"]), dict(n=5, state="open", title="Norwegian church reverb is too bright", body="High frequency content in reverb tail is distracting. EQ pre-send.", labels=["mix"]), dict(n=6, state="open", title="Granular density too high in intro", body="Start sparser and build. Currently too dense from bar 1.", labels=["texture"]), dict(n=7, state="closed", title="Phase correlation issues in stereo pad", body="Resolved by setting stereo width to 80% (was 120%).", labels=["mix"]), dict(n=8, state="open", title="Piano melody needs more dynamic variation", body="All notes at same velocity. Add cresc/dim on each 4-bar phrase.", labels=["piano", "dynamics"]), dict(n=9, state="open", title="Wind chimes pitched too high", body="7th partial sits above 8kHz on most speakers. Lower source pitch.", labels=["texture"]), dict(n=10, state="open", title="Generative seed produces repeated rhythmic clusters", body="Seed 42 has a bias toward beat 1 and 3. Rotate seed every 16 bars.", labels=["generative", "bug"]), dict(n=11, state="closed", title="Cello sustain too long — blurs transitions", body="Fixed — reduced release to 2s from 6s. Now transitions are audible.", labels=["strings"]), dict(n=12, state="open", title="Add breath sounds between sections", body="Silence between sections is too abrupt. A subtle room tone or breath sample would ease transitions.", labels=["texture", "arrangement"]), dict(n=13, state="open", title="LFO rate too fast on pad filter", body="0.1Hz LFO creates audible tremolo. Slow to 0.02Hz for imperceptible movement.", labels=["pad", "generative"]), dict(n=14, state="open", title="Mono bass under stereo pad causes phase issues", body="Bass is mono center, pad is 120° wide. Below 200Hz the combination cancels. HPF pad below 250Hz.", labels=["mix"]), dict(n=15, state="closed", title="Arp note lengths too uniform", body="Fixed — gate time now varies from 50% to 90% per note.", labels=["generative"]), ], "afrobeat": [ dict(n=1, state="open", title="Talking drum pattern needs more swing", body="Djembe is perfectly quantized — needs human timing ±5ms.", labels=["groove", "perc"]), dict(n=2, state="open", title="Highlife guitar pattern clash with bass", body="Both emphasise beat 1. Guitar should accent beats 2 and 4.", labels=["arrangement"]), dict(n=3, state="closed", title="Conga timing drift at bar 32", body="Fixed — re-quantized to 8th note grid.", labels=["perc", "timing"]), dict(n=4, state="open", title="Brass unison too thick — needs harmony", body="Four instruments in unison is thin. Split into 3-part harmony.", labels=["horns"]), dict(n=5, state="open", title="Vocal call-and-response timing off", body="Response phrases enter 1 beat early. Needs 4-beat gap.", labels=["vocals"]), dict(n=6, state="open", title="Add agogo bell pattern", body="The timeline/bell pattern is missing. Essential for afrobeat structure.", labels=["perc"]), dict(n=7, state="open", title="Bass slap too clicky at high velocity", body="Velocities above 100 produce unwanted transient click.", labels=["bass"]), dict(n=8, state="closed", title="Organ swell level too high", body="Reduced by -4dB. Now sits correctly behind guitar.", labels=["mix"]), dict(n=9, state="open", title="Yoruba lyric timing — stress on wrong syllable", body="Need input from native speaker on placement of tonal accent.", labels=["vocals", "cultural"]), dict(n=10, state="open", title="Add Talking Heads-style guitar texture", body="Open-string plucked guitar arpeggio on top of the rhythm section.", labels=["guitar"]), dict(n=11, state="open", title="Shekere part clashes with hi-hat", body="Both playing 16th pattern in the same register. Pan shekere hard right, hi-hat left.", labels=["perc", "mix"]), dict(n=12, state="closed", title="Bass register too muddy below 80Hz", body="Fixed — high-pass filter at 60Hz with 6dB/oct slope applied.", labels=["mix", "bass"]), dict(n=13, state="open", title="Trumpet solo needs call-and-response with guitar", body="Current solo is solo instrument only. Adding guitar responses every 2 bars would honor the tradition.", labels=["horns", "guitar", "arrangement"]), dict(n=14, state="open", title="Polyrhythm section needs tempo anchor", body="The 3-over-2 polyrhythm section lacks a clear pulse anchor. A kick on beat 1 every bar would help.", labels=["groove", "perc"]), dict(n=15, state="closed", title="Intro too long — listener disengages", body="Fixed — trimmed from 16 bars to 8 bars. Groove now enters at bar 9.", labels=["arrangement"]), ], "microtonal": [ # REPO_KEY_MAP key: "microtonal" dict(n=1, state="open", title="31-TET tuning table not loading on export", body="MIDI export falls back to 12-TET. Need to embed the tuning table in SysEx.", labels=["bug", "midi"]), dict(n=2, state="open", title="Neutral third interval sounds jarring in context", body="The 11/9 neutral third in bar 7 needs a resolving phrase. It hangs unresolved.", labels=["harmony"]), dict(n=3, state="closed", title="Playback pitch drift after bar 48", body="Fixed — DAW clock sync issue. Resolved by enabling MIDI clock.", labels=["bug"]), dict(n=4, state="open", title="Add justly-tuned overtone drone", body="A drone on the 5th partial (5/4 above root) would anchor the spectral harmony.", labels=["texture", "harmony"]), dict(n=5, state="open", title="Spectral voice leading too disjunct", body="Leaps of more than 7 steps in 31-TET feel chromatic. Stepwise motion preferred.", labels=["melody"]), dict(n=6, state="open", title="Cello bow speed inconsistency", body="Bow speed changes mid-phrase cause unintended dynamics. Normalize velocity curve.", labels=["strings"]), dict(n=7, state="closed", title="Score notation doesn't reflect microtonal accidentals", body="Fixed — using Helmholtz-Ellis notation for all quarter-tones.", labels=["notation"]), dict(n=8, state="open", title="Overtone series segment 8-16 missing", body="Partials 8-16 not included in the harmonic texture. Add soft flute tones for those partials.", labels=["harmony", "texture"]), dict(n=9, state="open", title="Attack transients too sharp in 31-TET scale runs", body="Fast runs in 31-TET sound percussive. Soften attack to 20ms.", labels=["dynamics"]), dict(n=10, state="closed", title="Tuning reference pitch wrong", body="Fixed — set A=432Hz as agreed for this piece.", labels=["tuning"]), dict(n=11, state="open", title="Add quarter-tone trill in cadential passage", body="The cadence (bars 22-24) lacks ornament. A quarter-tone trill on the leading tone would help.", labels=["melody", "ornament"]), dict(n=12, state="open", title="Sustain pedal creates pitch smear in 31-TET", body="Held notes at different 31-TET pitches ring together creating beating. Reduce pedal depth.", labels=["piano", "tuning"]), dict(n=13, state="open", title="Section 3 needs dynamic arc", body="Section 3 stays at mf throughout. Build from pp to ff over 16 bars.", labels=["dynamics"]), dict(n=14, state="closed", title="MIDI velocity map doesn't match 31-TET dynamics", body="Fixed — remapped velocity curve to match the intended dynamic nuance.", labels=["midi"]), dict(n=15, state="open", title="Missing rest in bar 19 causes overlap", body="Violin and cello overlap by one beat in bar 19. Insert an 8th rest.", labels=["notation", "bug"]), ], "drums": [ # REPO_KEY_MAP key: "drums" (REPO_DRUM_MACHINE) dict(n=1, state="open", title="808 kick too short — needs longer decay", body="Kick envelope decay at 0.1s sounds punchy but loses sub presence. Try 0.4s.", labels=["808", "drums"]), dict(n=2, state="open", title="Hi-hat pattern too rigid — needs humanize", body="All hats at 16th grid. Add ±8ms timing offset and velocity 60-90 range.", labels=["groove", "drums"]), dict(n=3, state="closed", title="Clap reverb tail too long", body="Fixed — reduced reverb to 0.8s. Clap now sits in the groove.", labels=["mix"]), dict(n=4, state="open", title="Add polyrhythmic hi-hat ostinato", body="Current pattern is 4/4 grid. Add a 3-against-4 hi-hat line as a layer.", labels=["groove", "drums"]), dict(n=5, state="open", title="Modular snare too bright at 4kHz", body="High transient spike at 4kHz sounds harsh. EQ notch at 4kHz, -4dB, Q=2.", labels=["mix", "drums"]), dict(n=6, state="closed", title="Kick/bass frequency masking", body="Fixed — sidechain compression on bass triggered by kick.", labels=["mix"]), dict(n=7, state="open", title="Pattern variation needed at bar 17", body="The pattern repeats unmodified for 16 bars. Add a fill at bar 17.", labels=["arrangement", "drums"]), dict(n=8, state="open", title="808 tuning: needs pitch envelope", body="Kick pitch stays flat. A fast downward pitch sweep (1 octave, 50ms) would be more musical.", labels=["808"]), dict(n=9, state="open", title="Tom fills too frequent", body="Tom fills every 4 bars interrupt the groove flow. Reduce to every 8 bars.", labels=["drums"]), dict(n=10, state="closed", title="Crash cymbal sample too long", body="Fixed — trimmed to 2s with fade-out.", labels=["drums", "mix"]), dict(n=11, state="open", title="Polyrhythm section: 5-against-4 too abrupt", body="The shift to 5-against-4 at bar 33 needs 2 bars of transition.", labels=["groove", "arrangement"]), dict(n=12, state="open", title="Missing ghost note pattern in verse", body="Verse section lacks ghost notes — pattern sounds flat. Add 16th ghosts at velocity 25-35.", labels=["drums", "groove"]), dict(n=13, state="closed", title="Open hi-hat not choked by closed hat", body="Fixed — added hat-choke controller message at each closed hat hit.", labels=["drums", "midi"]), dict(n=14, state="open", title="Rimshot too loud relative to snare", body="Rimshot peaks 3dB above snare. Level down or use snare for fills.", labels=["mix", "drums"]), dict(n=15, state="open", title="Add shaker for 16th-note pulse reference", body="The groove loses its feel at slower tempo passages. A shaker pulse would anchor the listener.", labels=["perc", "groove"]), ], "chanson": [ dict(n=1, state="open", title="Piano left hand too busy in verse", body="Alberti bass pattern is too active for chanson miniature style. Try sparse block chords.", labels=["piano", "arrangement"]), dict(n=2, state="open", title="Cello pizzicato needs more resonance", body="Pizzicato sounds thin at 52 BPM. Add short room reverb to give note length.", labels=["strings"]), dict(n=3, state="closed", title="Piano sustain pedal creates blur in slow passage", body="Fixed — split pedaling technique applied to maintain harmonic clarity.", labels=["piano"]), dict(n=4, state="open", title="Melody too narrow — stays in A4-E5 range", body="Expand downward to A3. The lower register gives a more intimate chanson character.", labels=["melody"]), dict(n=5, state="open", title="Final cadence needs ritardando", body="The piece ends metrically. A gradual slow-down over 2 bars would give weight to the ending.", labels=["arrangement", "dynamics"]), dict(n=6, state="open", title="Add optional accordion doubling", body="Chanson tradition supports musette accordion. A soft doubling of the piano melody would be idiomatic.", labels=["arrangement"]), dict(n=7, state="closed", title="Cello bowing direction markers missing", body="Fixed — down-bows on beats 1 and 3, up-bows on 2 and 4.", labels=["strings", "notation"]), dict(n=8, state="open", title="Bridge modulation to C# minor too abrupt", body="The pivot chord (E major = shared dominant) should be held for 2 bars before modulating.", labels=["harmony"]), dict(n=9, state="open", title="Silence sections too short", body="Pierre's style uses 4-bar silences. Current rests are only 2 bars — double them.", labels=["arrangement", "dynamics"]), dict(n=10, state="closed", title="Notation: accidentals not consistent", body="Fixed — standardized to sharps throughout (A major context).", labels=["notation"]), dict(n=11, state="open", title="Piano voicing too wide in left hand", body="Bass notes below C2 sound muddy on a grand piano. Raise left hand by an octave.", labels=["piano"]), dict(n=12, state="open", title="Add pedal marking for the coda", body="Coda (bars 28-32) has no pedal indication. The color should be hazy — add una corda.", labels=["piano", "dynamics"]), dict(n=13, state="open", title="Tempo too fast for lyric melancholy", body="52 BPM feels hurried for this material. Try 44 BPM — aligns with Pierre's reference recordings.", labels=["arrangement"]), dict(n=14, state="closed", title="Cello enters too early in bar 5", body="Fixed — shifted cello entrance to bar 6 beat 1.", labels=["strings", "timing"]), dict(n=15, state="open", title="Middle section lacks harmonic tension", body="The A major tonality is too stable for 8 bars. Introduce a borrowed chord (mode mixture) at bar 20.", labels=["harmony"]), ], "granular": [ dict(n=1, state="open", title="Grain density parameter too uniform", body="30 grains/sec is constant throughout. Modulate between 5 and 80 for organic feel.", labels=["generative", "texture"]), dict(n=2, state="open", title="Source sample quality too clean", body="Found sounds should be degraded. Add vinyl noise and room tone before granulating.", labels=["texture"]), dict(n=3, state="closed", title="Grain size too small — produces clicks", body="Fixed — minimum grain size set to 40ms (was 5ms). Click-free.", labels=["bug", "generative"]), dict(n=4, state="open", title="Pitch randomization range too wide", body="±1 octave pitch spread sounds noisy, not musical. Constrain to ±major 3rd.", labels=["generative", "pitch"]), dict(n=5, state="open", title="Add grain position automation over time", body="Reading from fixed position 0.3 in the source. Automate position 0.0→1.0 over 4 minutes.", labels=["generative"]), dict(n=6, state="closed", title="Stereo pan spread too narrow", body="Fixed — grain pan randomization set to ±45° (was ±10°).", labels=["mix"]), dict(n=7, state="open", title="Texture layer too loud vs piano layer", body="Granular texture sits 6dB above the piano melody. Attenuate texture by -6dB.", labels=["mix"]), dict(n=8, state="open", title="Add resonant filter sweep through granular cloud", body="A slow bandpass filter sweep (0.05Hz) through the grain cloud would create hypnotic movement.", labels=["texture", "generative"]), dict(n=9, state="open", title="Grain envelope too flat — no transients", body="All grains use linear envelope. Add a fast attack (2ms) + slow decay for percussion-like texture.", labels=["generative"]), dict(n=10, state="closed", title="Found sound source too recognizable", body="Fixed — source pitch-shifted and time-stretched until original is unrecognizable.", labels=["texture"]), dict(n=11, state="open", title="Feedback loop creates unwanted oscillation", body="Grain output fed back into input causes 12Hz oscillation at high density. Add DC blocker.", labels=["bug", "generative"]), dict(n=12, state="open", title="Density automation ramp too abrupt", body="The jump from 5 to 80 grains/sec happens over 1 bar. Needs 4-bar ramp for smooth transition.", labels=["generative", "arrangement"]), dict(n=13, state="closed", title="Grain position quantized to beat grid", body="Fixed — position now continuous with subtle clock jitter (±10ms).", labels=["generative"]), dict(n=14, state="open", title="Violin source needs more bow noise character", body="Current sample is too pure. A sul ponticello bowing noise layer would add grit.", labels=["texture"]), dict(n=15, state="open", title="Outro needs silence interruption", body="The granular outro should be punctuated by 500ms silences every 8 bars — gaps in the cloud.", labels=["arrangement", "texture"]), ], "funk-suite": [ # REPO_KEY_MAP key: "funk-suite" (REPO_FUNK_SUITE) dict(n=1, state="open", title="Electric piano comping too dense in verse", body="Clavinet and Rhodes both comp simultaneously. Pick one per section.", labels=["arrangement"]), dict(n=2, state="open", title="Wah bass envelope too slow", body="Wah envelope follows ADSR but attack is 80ms — loses the click. Set to 10ms.", labels=["bass"]), dict(n=3, state="closed", title="Hi-hat and shaker doubling causes flamming", body="Fixed — hi-hat quantized to 16th grid, shaker humanized separately.", labels=["drums", "timing"]), dict(n=4, state="open", title="Horns need staccato articulation in bars 9-16", body="Horn stabs are held too long. 16th-note staccato would give the funk punch.", labels=["horns"]), dict(n=5, state="open", title="Clavinet tone too trebly", body="Clavinet without a low-pass filter sounds harsh. A gentle 3kHz shelf would smooth it.", labels=["mix"]), dict(n=6, state="closed", title="Groove falls apart at bar 25", body="Fixed — kick pattern re-programmed with 16th anticipation on beat 3.", labels=["groove", "drums"]), dict(n=7, state="open", title="Movement IV needs a climactic peak", body="Movement IV builds but never peaks before the final cadence. Add a unison hit at bar 48.", labels=["arrangement", "dynamics"]), dict(n=8, state="open", title="Bass slap velocity too uniform", body="All slap notes at velocity 110. Alternate strong (120) and weak (90) for dynamic groove.", labels=["bass", "groove"]), dict(n=9, state="open", title="Pocket drum fills too predictable at phrase ends", body="Fill every 4 bars, always a snare run. Vary: sometimes a kick + tom, sometimes silence.", labels=["drums", "arrangement"]), dict(n=10, state="closed", title="Chord voicings too thick in bridge", body="Fixed — reduced to 3-voice drop-2 voicing in brass section.", labels=["harmony"]), dict(n=11, state="open", title="Add octave unison in horn section for climax", body="The climax in Movement III (bar 56) lacks weight. Add trombones an octave below trumpets.", labels=["horns", "arrangement"]), dict(n=12, state="open", title="Movement II transition too abrupt", body="Movement I ends, Movement II starts without transition. Add 2-bar breakdown.", labels=["arrangement"]), dict(n=13, state="closed", title="Clavinet out of tune with Rhodes", body="Fixed — both set to A=440 reference. Clavinet detuned by +12 cents.", labels=["tuning"]), dict(n=14, state="open", title="Shaker too loud in mix", body="Shaker sits 4dB above hi-hat in the mid-range. Reduce shaker -4dB.", labels=["mix"]), dict(n=15, state="open", title="Final movement needs a proper ending", body="Suite ends with a fade-out which is too passive. Write a 4-bar coda with a unison hit.", labels=["arrangement"]), ], "jazz-trio": [ # REPO_KEY_MAP key: "jazz-trio" (REPO_JAZZ_TRIO) dict(n=1, state="open", title="Piano left hand too busy during bass solo", body="Left-hand comp fills every bar during the bass solo — player needs space.", labels=["piano", "arrangement"]), dict(n=2, state="open", title="Brushed snare too loud in A section", body="Brushes are competing with piano in the same frequency range. Reduce snare by -3dB.", labels=["drums", "mix"]), dict(n=3, state="closed", title="Walking bass accidentally doubles piano left hand", body="Fixed — bass transposed up a 10th in bars 5-8 to avoid doubling.", labels=["bass"]), dict(n=4, state="open", title="Tempo rushes during piano solo", body="Drummer accelerates during piano solo — common problem. Add a click track reference.", labels=["tempo", "groove"]), dict(n=5, state="open", title="Add ritardando at end of each chorus", body="Each 32-bar chorus ends metrically. A slight rit in the last 2 bars would honor the standard.", labels=["arrangement", "dynamics"]), dict(n=6, state="closed", title="Bass pizzicato too short — sounds staccato", body="Fixed — gate time extended to 90% of note duration.", labels=["bass"]), dict(n=7, state="open", title="Cymbal swell missing at the top of each chorus", body="A ride cymbal swell on beat 4 of bar 32 would signal the chorus repeat elegantly.", labels=["drums", "arrangement"]), dict(n=8, state="open", title="Piano voicing too sparse in outer choruses", body="Shell voicings (root + 7th only) are too thin in the outer A sections. Add 3rds.", labels=["piano", "harmony"]), dict(n=9, state="open", title="Standards melody not centered in mix", body="Piano melody sits behind the rhythm section. Bump piano +2dB during head.", labels=["mix"]), dict(n=10, state="closed", title="Bass note durations overlap chord changes", body="Fixed — note-offs now aligned to beat boundaries before chord changes.", labels=["bass"]), dict(n=11, state="open", title="Add sus chord before final turnaround", body="The final turnaround (bars 29-32) lacks a sus chord to build tension before the resolution.", labels=["harmony"]), dict(n=12, state="open", title="Ride cymbal bell too prominent", body="Bell accent on beat 2 and 4 cuts through the texture. Use shoulder of stick instead.", labels=["drums"]), dict(n=13, state="closed", title="Piano octaves in outro too thick", body="Fixed — reduced to single melody line in the final 4 bars.", labels=["piano"]), dict(n=14, state="open", title="Tag repeat needs extra bar", body="Standard convention adds 1 extra bar at the final tag. Currently not present.", labels=["arrangement"]), dict(n=15, state="open", title="Double-time feel section needs hi-hat switch", body="During double-time feel (bars 17-24), hi-hat should move to 2-beat pattern. Currently stays on 4.", labels=["drums", "groove"]), ], } # Use a generic template for repos without specific issue templates (fork repos) GENERIC_ISSUES = [ dict(n=1, state="open", title="Energy drops in the middle section", body="The arrangement loses momentum around bar 24-32. Add element to sustain interest.", labels=["arrangement", "energy"]), dict(n=2, state="open", title="Dynamics too compressed", body="The quietest and loudest moments are within 3dB. Needs more dynamic range.", labels=["mix", "dynamics"]), dict(n=3, state="closed", title="Tempo inconsistency between sections", body="Fixed by applying strict quantize to all MIDI.", labels=["timing"]), dict(n=4, state="open", title="Add a counter-melody", body="The main melody is unaccompanied for too long. Add a secondary voice.", labels=["arrangement"]), dict(n=5, state="open", title="Harmonic rhythm too fast in verse", body="Chord changes every 2 beats feels rushed. Try 4-beat chord duration.", labels=["harmony"]), dict(n=6, state="closed", title="Mix: low end muddy", body="Resolved — high-pass filter below 80Hz on all non-bass instruments.", labels=["mix"]), dict(n=7, state="open", title="Transition between sections too abrupt", body="The jump from section A to B lacks a linking phrase. Add a 2-bar turnaround.", labels=["arrangement"]), dict(n=8, state="open", title="Lead instrument too forward in mix", body="The melody sits 6dB above the supporting texture. Reduce by 3dB and add subtle delay.", labels=["mix"]), dict(n=9, state="closed", title="Reverb tail bleeds into silence", body="Fixed — reduced reverb pre-delay to 20ms, decay to 1.5s.", labels=["mix"]), dict(n=10, state="open", title="Add introduction before main theme", body="The piece starts on the main theme with no setup. A 4-bar intro would establish context.", labels=["arrangement"]), dict(n=11, state="open", title="Velocity variation too narrow", body="All MIDI velocities within 90-110 range. Expand to 60-127 for natural expression.", labels=["dynamics"]), dict(n=12, state="open", title="Stereo field too narrow", body="Mix is mostly center-panned. Pan secondary voices hard left/right for width.", labels=["mix"]), dict(n=13, state="closed", title="Quantization too tight — sounds mechanical", body="Fixed — applied 75% quantize (humanize 25%).", labels=["groove", "timing"]), dict(n=14, state="open", title="Ending lacks finality", body="The piece fades out rather than closing with a defined cadence. Write a 2-bar coda.", labels=["arrangement"]), dict(n=15, state="open", title="High frequency content harsh on headphones", body="Content above 8kHz is piercing. A gentle high shelf cut -3dB above 8kHz would help.", labels=["mix"]), ] # --------------------------------------------------------------------------- # Milestone templates per repo-key — title, description, state, due offset # --------------------------------------------------------------------------- MILESTONE_TEMPLATES: dict[str, list[dict[str, Any]]] = { "neo-soul": [ dict(n=1, title="Album v1.0", state="open", description="Full release of Neo-Soul Experiment Vol. 1. All tracks mixed and mastered.", due_days=60), dict(n=2, title="Mixing Complete", state="closed", description="All tracks signed off by mixing engineer. Ready for mastering.", due_days=None), ], "modal-jazz": [ dict(n=1, title="Session Complete", state="open", description="All Modal Jazz Sketches tracks recorded and approved.", due_days=30), ], "ambient": [ dict(n=1, title="Vol. 1 Complete", state="open", description="All Ambient Textures Vol. 1 compositions finalised.", due_days=45), dict(n=2, title="Mastering Done", state="closed", description="Mastering session at Abbey Road complete.", due_days=None), ], "afrobeat": [ dict(n=1, title="Album Launch", state="open", description="Afrobeat Grooves album release. All 12 tracks production-ready.", due_days=30), dict(n=2, title="v1.0 Recording", state="closed", description="All live tracking sessions completed.", due_days=None), ], "microtonal": [ dict(n=1, title="Études Complete", state="open", description="All 10 microtonal études composed, engraved, and recorded.", due_days=90), ], "drums": [ dict(n=1, title="808 Variations v1.0", state="open", description="All drum variations composed and exported as stems.", due_days=20), ], "chanson": [ dict(n=1, title="Score Publication", state="open", description="Score submitted to publisher for Chanson Minimale edition.", due_days=45), ], "granular": [ dict(n=1, title="Research Complete", state="open", description="All granular synthesis research documented and recordings exported.", due_days=60), ], "funk-suite": [ dict(n=1, title="Suite Release", state="open", description="Funk Suite No. 1 — all four movements completed and sequenced.", due_days=25), dict(n=2, title="Mvt. I–II Done", state="closed", description="Movements I and II approved by the full ensemble.", due_days=None), ], "jazz-trio": [ dict(n=1, title="Album Complete", state="open", description="Jazz Trio Sessions album — all takes selected and arranged.", due_days=40), ], } # Issues in each milestone (by issue number n) — controls milestone_id assignment MILESTONE_ISSUE_ASSIGNMENTS: dict[str, dict[int, list[int]]] = { # key: repo_key → {milestone_n: [issue_n, ...]} "neo-soul": {1: [1, 2, 4, 5, 7, 8, 10, 11, 12, 14, 15], 2: [3, 6, 9, 13]}, "modal-jazz": {1: [1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 15]}, "ambient": {1: [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15], 2: [3, 7, 11]}, "afrobeat": {1: [1, 2, 4, 5, 6, 7, 9, 10, 11, 13, 14, 15], 2: [3, 8, 12]}, "microtonal": {1: [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15]}, "drums": {1: [1, 2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15]}, "chanson": {1: [1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15]}, "granular": {1: [1, 2, 4, 5, 8, 9, 10, 12, 14, 15]}, "funk-suite": {1: [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15], 2: [3, 7, 11]}, "jazz-trio": {1: [1, 2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15]}, } # --------------------------------------------------------------------------- # Issue comment templates # --------------------------------------------------------------------------- ISSUE_COMMENT_BODIES: list[str] = [ "Agreed — I noticed this too during the last session. @{mention} have you tried adjusting the velocity curve?", "Good catch. The `{track}` track in `section:{section}` is definitely the culprit here.", "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```", "This has been bothering me since the first mix. The `beats:{beats}` region needs attention.", "@{mention} — can you take a look? This is blocking the v1.0 milestone.", "Fixed in my local branch. The root cause was the `{track}` MIDI channel assignment. Will open a PR.", "I ran an analysis on the affected region:\n```\nFreq: {freq}Hz Peak: -6dBFS Phase: +12°\n```\nNeeds a notch filter.", "Confirmed on my system. Happens consistently at bar {bar}. The {track} seems off.", "Not sure this is the right approach. @{mention} what do you think about using a different technique?", "This is now tracked in the v1.0 milestone. Should be resolved before release.", "After further listening, the issue is more subtle than I initially thought. The `section:{section}` transition is the real problem.", "Tested the fix — sounds much better now. The `track:{track}` now sits properly in the mix.", "Adding context: this is related to #3 which had the same root cause in `section:{section}`.", "I think we should prioritize this. The groove feels off and it's the first thing listeners will notice.", "Will take a pass at this during the next session. @{mention} — can you prepare a reference recording?", ] ISSUE_COMMENT_MENTIONS = ["gabriel", "sofia", "marcus", "yuki", "aaliya", "chen", "fatou", "pierre"] ISSUE_COMMENT_TRACKS = ["bass", "keys", "drums", "strings", "horns", "guitar", "vocals", "pad"] ISSUE_COMMENT_SECTIONS = ["intro", "verse", "chorus", "bridge", "breakdown", "coda", "outro"] ISSUE_COMMENT_FREQS = ["80", "200", "400", "800", "2000", "4000", "8000"] def _make_issue_comment_body(seed: int) -> str: """Generate a realistic issue comment body with @mention, track refs, and code blocks.""" template = ISSUE_COMMENT_BODIES[seed % len(ISSUE_COMMENT_BODIES)] mention = ISSUE_COMMENT_MENTIONS[(seed + 1) % len(ISSUE_COMMENT_MENTIONS)] track = ISSUE_COMMENT_TRACKS[seed % len(ISSUE_COMMENT_TRACKS)] section = ISSUE_COMMENT_SECTIONS[(seed + 2) % len(ISSUE_COMMENT_SECTIONS)] bar = (seed % 32) + 1 freq = ISSUE_COMMENT_FREQS[seed % len(ISSUE_COMMENT_FREQS)] return (template .replace("{mention}", mention) .replace("{track}", track) .replace("{section}", section) .replace("{bar}", str(bar)) .replace("{beats}", f"{bar}-{bar+4}") .replace("{freq}", freq)) def _make_issue_musical_refs(body: str) -> list[dict[str, str]]: """Extract musical context references from a comment body.""" import re refs: list[dict[str, str]] = [] for m in re.finditer(r"track:(\w+)", body): refs.append({"type": "track", "value": m.group(1)}) for m in re.finditer(r"section:(\w+)", body): refs.append({"type": "section", "value": m.group(1)}) for m in re.finditer(r"beats:(\d+-\d+)", body): refs.append({"type": "beats", "value": m.group(1)}) return refs # --------------------------------------------------------------------------- # PR templates # --------------------------------------------------------------------------- def _make_prs(repo_id: str, commits: list[dict[str, Any]], owner: str) -> list[dict[str, Any]]: """Generate 4 template pull requests (open, merged, open, closed) for a repo.""" if len(commits) < 4: return [] c = commits return [ dict(pr_id=_uid(f"pr-{repo_id}-1"), repo_id=repo_id, title="Feat: add counter-melody layer", body="## Changes\nAdds secondary melodic voice.\n\n## Analysis\nHarmonic tension +0.08.", state="open", from_branch="feat/counter-melody", to_branch="main", author=owner, created_at=_now(days=6)), dict(pr_id=_uid(f"pr-{repo_id}-2"), repo_id=repo_id, title="Refactor: humanize all MIDI timing", body="Applied `muse humanize --natural` to all tracks. Groove score +0.12.", state="merged", from_branch="fix/humanize-midi", to_branch="main", merge_commit_id=c[-3]["commit_id"], author=owner, created_at=_now(days=14)), dict(pr_id=_uid(f"pr-{repo_id}-3"), repo_id=repo_id, title="Experiment: alternate bridge harmony", body="Trying a tritone substitution approach for the bridge section.", state="open", from_branch="experiment/bridge-harmony", to_branch="main", author=owner, created_at=_now(days=3)), dict(pr_id=_uid(f"pr-{repo_id}-4"), repo_id=repo_id, title="Fix: resolve voice-leading errors", body="Parallel 5ths in bars 7-8 and parallel octaves in bars 15-16 corrected.", state="closed", from_branch="fix/voice-leading", to_branch="main", author=owner, created_at=_now(days=20)), ] # --------------------------------------------------------------------------- # Release templates # --------------------------------------------------------------------------- def _make_releases(repo_id: str, commits: list[dict[str, Any]], repo_name: str, owner: str) -> list[dict[str, Any]]: """Generate 3 releases (v0.1.0 draft, v0.2.0 arrangement, v1.0.0 full) for a repo. Each release dict includes a deterministic ``release_id`` so downstream code (release assets seeding) can reference it without a separate DB query. """ if not commits: return [] return [ dict(release_id=_uid(f"release-{repo_id}-v0.1.0"), repo_id=repo_id, tag="v0.1.0", title="Early Draft", 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.", commit_id=commits[min(4, len(commits)-1)]["commit_id"], download_urls={"midi_bundle": f"/releases/{repo_id}-v0.1.0.zip"}, author=owner, created_at=_now(days=45)), dict(release_id=_uid(f"release-{repo_id}-v0.2.0"), repo_id=repo_id, tag="v0.2.0", title="Arrangement Draft", 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", commit_id=commits[min(12, len(commits)-1)]["commit_id"], download_urls={"midi_bundle": f"/releases/{repo_id}-v0.2.0.zip", "mp3": f"/releases/{repo_id}-v0.2.0.mp3"}, author=owner, created_at=_now(days=25)), dict(release_id=_uid(f"release-{repo_id}-v1.0.0"), repo_id=repo_id, tag="v1.0.0", title=f"{repo_name} — Full Release", 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", commit_id=commits[-1]["commit_id"], download_urls={"midi_bundle": f"/releases/{repo_id}-v1.0.0.zip", "mp3": f"/releases/{repo_id}-v1.0.0.mp3", "stems": f"/releases/{repo_id}-v1.0.0-stems.zip"}, author=owner, created_at=_now(days=5)), ] # --------------------------------------------------------------------------- # Session templates # --------------------------------------------------------------------------- def _make_sessions(repo_id: str, owner: str, commits: list[dict[str, Any]]) -> list[dict[str, Any]]: """Generate 6 collaboration sessions per repo; adds a live session for high-traffic repos.""" if len(commits) < 2: return [] sess = [] collab_map: dict[str, list[tuple[str, ...]]] = { REPO_NEO_SOUL: [("gabriel", "marcus"), ("gabriel",), ("gabriel", "marcus", "aaliya")], REPO_MODAL_JAZZ: [("gabriel", "marcus"), ("gabriel",)], REPO_AMBIENT: [("sofia", "yuki"), ("sofia",), ("sofia", "pierre")], REPO_AFROBEAT: [("aaliya", "fatou"), ("aaliya",), ("aaliya", "marcus")], REPO_FUNK_SUITE: [("marcus", "gabriel"), ("marcus",)], REPO_JAZZ_TRIO: [("marcus",), ("marcus", "gabriel")], REPO_DRUM_MACHINE: [("fatou",), ("fatou", "aaliya")], REPO_CHANSON: [("pierre",), ("pierre", "sofia")], REPO_GRANULAR: [("yuki",), ("yuki", "sofia")], REPO_MICROTONAL: [("chen",)], } collab_groups: list[tuple[str, ...]] = collab_map.get(repo_id) or [(owner,)] locations = [ "Studio A, São Paulo", "Home studio", "Remote", "Abbey Road Studio 3", "Electric Lady Studios", "Remote (async)", "Bedroom studio, Tokyo", "La Fabrique, Marseille", ] for i, group in enumerate((collab_groups * 3)[:6]): start_days = 60 - i * 8 dur_hours = [3, 4, 2, 5, 2, 3][i % 6] commit_slice = commits[i * 4:i * 4 + 3] if len(commits) > i * 4 + 3 else commits[-2:] sess.append(dict( session_id=_uid(f"sess-{repo_id}-{i}"), repo_id=repo_id, started_at=_now(days=start_days), ended_at=_now(days=start_days, hours=-dur_hours), participants=list(group), location=locations[i % len(locations)], intent=f"Session {i+1}: extend arrangement and refine mix", commits=[c["commit_id"] for c in commit_slice], notes=f"Productive session. Focused on {'groove' if i % 2 == 0 else 'arrangement'}.", is_active=False, created_at=_now(days=start_days), )) # Add one live/active session for the first big repo if repo_id in (REPO_NEO_SOUL, REPO_AFROBEAT): sess.append(dict( session_id=_uid(f"sess-{repo_id}-live"), repo_id=repo_id, started_at=_now(hours=1), ended_at=None, participants=[owner, "marcus"], location="Studio A — Live", intent="Live recording session — tracking final takes", commits=[], notes="", is_active=True, created_at=_now(hours=1), )) return sess # --------------------------------------------------------------------------- # Webhook + delivery templates # --------------------------------------------------------------------------- def _make_webhooks( repo_id: str, owner: str ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: """Generate 3 webhook subscriptions and 10–15 deliveries per webhook. Delivery outcomes cycle through 200 (success), 500 (server error), and 0/timeout patterns so every status is represented in the dataset. Returns (webhooks, deliveries). """ webhooks: list[dict[str, Any]] = [] deliveries: list[dict[str, Any]] = [] wh_configs = [ dict(suffix="push", url=f"https://hooks.example.com/{owner}/push", events=["push"], active=True), dict(suffix="pr", url=f"https://hooks.example.com/{owner}/pr", events=["pull_request", "issue"], active=True), dict(suffix="release", url=f"https://hooks.example.com/{owner}/release", events=["release"], active=False), ] for wh_spec in wh_configs: wh_suffix: str = wh_spec["suffix"] # type: ignore[assignment] wh_url: str = wh_spec["url"] # type: ignore[assignment] wh_events: list[str] = wh_spec["events"] # type: ignore[assignment] wh_active: bool = wh_spec["active"] # type: ignore[assignment] wh_id = _uid(f"wh-{repo_id}-{wh_suffix}") webhooks.append(dict( webhook_id=wh_id, repo_id=repo_id, url=wh_url, events=wh_events, secret=_sha(f"secret-{repo_id}-{wh_suffix}")[:32], active=wh_active, created_at=_now(days=60), )) # 10–15 deliveries per webhook; cycle through status patterns. n_deliveries = 10 + (int(_sha(f"nd-{repo_id}-{wh_suffix}")[:2], 16) % 6) for j in range(n_deliveries): # Pattern (period 7): 200, 200, 500, 200, 200, timeout, 200 pattern = j % 7 if pattern in (0, 1, 3, 4, 6): success, status, resp_body = True, 200, '{"ok": true}' elif pattern == 2: success, status, resp_body = False, 500, '{"error": "Internal Server Error"}' else: # pattern == 5 → timeout success, status, resp_body = False, 0, "" event_type = wh_events[j % len(wh_events)] payload_data = ( f'{{"event": "{event_type}", "repo": "{repo_id}", ' f'"attempt": {(j % 3) + 1}, "ts": "{_now(days=j).isoformat()}"}}' ) deliveries.append(dict( delivery_id=_uid(f"del-{wh_id}-{j}"), webhook_id=wh_id, event_type=event_type, payload=payload_data, attempt=(j % 3) + 1, success=success, response_status=status, response_body=resp_body, delivered_at=_now(days=j), )) return webhooks, deliveries # --------------------------------------------------------------------------- # PR comment templates # --------------------------------------------------------------------------- _PR_COMMENT_POOL: list[dict[str, Any]] = [ dict(target_type="general", target_track=None, target_beat_start=None, target_beat_end=None, target_note_pitch=None, body="Overall approach looks good. The counter-melody adds the harmonic tension the arrangement was missing. LGTM with minor comments below."), dict(target_type="track", target_track="bass", target_beat_start=None, target_beat_end=None, target_note_pitch=None, 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."), dict(target_type="region", target_track="keys", target_beat_start=9.0, target_beat_end=17.0, target_note_pitch=None, body="Bars 9-16: the Rhodes voicing here feels crowded. Try removing the 5th — root + 3rd + 7th is cleaner."), dict(target_type="note", target_track="trumpet", target_beat_start=24.0, target_beat_end=None, target_note_pitch=84, body="This high C (MIDI 84) at beat 24 is above idiomatic range. Transpose down an octave to C5 (MIDI 72)."), dict(target_type="general", target_track=None, target_beat_start=None, target_beat_end=None, target_note_pitch=None, body="The tritone substitution is elegant. I'd approve as-is but @gabriel should confirm alignment with the arrangement plan."), dict(target_type="track", target_track="drums", target_beat_start=None, target_beat_end=None, target_note_pitch=None, 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."), dict(target_type="region", target_track="strings", target_beat_start=25.0, target_beat_end=33.0, target_note_pitch=None, body="Bars 25-32: pizzicato countermelody is beautifully voiced. Staggered entries work well. No changes needed."), dict(target_type="note", target_track="bass", target_beat_start=13.0, target_beat_end=None, target_note_pitch=42, body="This F# (MIDI 42) at beat 13 creates a very dark sub. If intentional — great. If not, try B1 (MIDI 47)."), dict(target_type="general", target_track=None, target_beat_start=None, target_beat_end=None, target_note_pitch=None, 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."), dict(target_type="track", target_track="vocals", target_beat_start=None, target_beat_end=None, target_note_pitch=None, body="Vocal sibilance is still present on sustained S sounds. De-esser threshold needs to be 3dB lower. Otherwise the PR is ready."), ] def _make_pr_comments( pr_id: str, repo_id: str, pr_n: int, owner: str, days_ago: int ) -> list[dict[str, Any]]: """Generate 3–8 inline review comments for a single pull request. Uses the rotating _PR_COMMENT_POOL with author cycling across the user roster. Some comments thread as replies via parent_comment_id. """ pool_size = len(_PR_COMMENT_POOL) authors = ["gabriel", "sofia", "marcus", "yuki", "aaliya", "chen", "fatou", "pierre"] n_comments = 3 + (pr_n % 6) # yields 3–8 per PR comments: list[dict[str, Any]] = [] first_comment_id: str | None = None for i in range(n_comments): pool_entry = _PR_COMMENT_POOL[(pr_n * 5 + i * 3) % pool_size] author = authors[(pr_n + i + 2) % len(authors)] if author == owner and i == 0: author = authors[(pr_n + i + 3) % len(authors)] comment_id = _uid(f"pr-cmt-{repo_id}-{pr_n}-{i}") parent_id: str | None = None # Comments 3+ thread under the first comment to simulate replies. if i >= 3 and first_comment_id is not None: parent_id = first_comment_id comments.append(dict( comment_id=comment_id, pr_id=pr_id, repo_id=repo_id, author=author, body=pool_entry["body"], target_type=pool_entry["target_type"], target_track=pool_entry["target_track"], target_beat_start=pool_entry["target_beat_start"], target_beat_end=pool_entry["target_beat_end"], target_note_pitch=pool_entry["target_note_pitch"], parent_comment_id=parent_id, created_at=_now(days=days_ago - i), )) if i == 0: first_comment_id = comment_id return comments # --------------------------------------------------------------------------- # Main seed function # --------------------------------------------------------------------------- async def seed(db: AsyncSession, force: bool = False) -> None: """Populate all MuseHub tables with a realistic stress-test dataset. Inserts users, repos, commits, branches, issues, PRs, releases, sessions, social graph (stars, follows, watches, comments, reactions, notifications, forks, view/download events), and the full Muse VCS layer (objects, snapshots, commits, tags). Pass force=True to wipe and re-seed existing data. """ print("🌱 Seeding MuseHub stress-test dataset…") result = await db.execute(text("SELECT COUNT(*) FROM musehub_repos")) existing = result.scalar() or 0 if existing > 0 and not force: print(f" ⚠️ {existing} repo(s) already exist — skipping. Pass --force to wipe and reseed.") _print_urls() return if existing > 0 and force: print(" 🗑 --force: clearing existing seed data…") for tbl in [ # Conversation children first "maestro_message_actions", "maestro_conversation_messages", "maestro_conversations", "maestro_usage_logs", "maestro_access_tokens", # Muse variation children first (FK order) "muse_note_changes", "muse_phrases", "muse_variations", # Muse VCS — innermost first (tags depend on commits, commits depend on snapshots) "muse_tags", "muse_commits", "muse_snapshots", "muse_objects", # MuseHub — children before parents (FK order) "musehub_download_events", "musehub_view_events", "musehub_forks", "musehub_notifications", "musehub_watches", "musehub_follows", "musehub_reactions", "musehub_comments", "musehub_render_jobs", "musehub_events", # Stash children before stash (FK to maestro_users + repos) "musehub_stash_entries", "musehub_stash", # Collaborators (FK to maestro_users + repos) "musehub_collaborators", "musehub_stars", "musehub_sessions", # Release assets before releases "musehub_release_assets", "musehub_releases", "musehub_webhook_deliveries", "musehub_webhooks", # PR children before pull_requests "musehub_pr_labels", "musehub_pr_comments", "musehub_pr_reviews", "musehub_pull_requests", # Issue children before issues; milestones after (SET NULL FK) "musehub_issue_labels", "musehub_issue_milestones", "musehub_issue_comments", "musehub_issues", "musehub_milestones", # Labels after issues/PRs "musehub_labels", "musehub_branches", "musehub_objects", "musehub_commits", "musehub_repos", "musehub_profiles", # maestro_users last (other tables FK to it) "maestro_users", ]: await db.execute(text(f"DELETE FROM {tbl}")) await db.flush() # ── 1. maestro_users (required FK for collaborators + stash) ───────────── # Mirrors the same user IDs used in musehub_profiles so the FK chain is # consistent across the whole schema. all_user_ids_and_names = list(USERS) + list(COMPOSER_USERS) for uid, _uname, _bio in all_user_ids_and_names: db.add(User( id=uid, budget_cents=2500, budget_limit_cents=5000, created_at=_now(days=120), updated_at=_now(days=1), )) print(f" ✅ maestro_users: {len(all_user_ids_and_names)}") await db.flush() # ── 1b. User profiles (musehub_profiles) ────────────────────── # Pinned repos show the owner's most prominent repos on their profile page. _PROFILE_PINS: dict[str, list[str]] = { GABRIEL: [REPO_NEO_SOUL, REPO_MODAL_JAZZ, REPO_NEO_BAROQUE, REPO_COMMUNITY], SOFIA: [REPO_AMBIENT], MARCUS: [REPO_FUNK_SUITE, REPO_JAZZ_TRIO, REPO_RAGTIME_EDM], YUKI: [REPO_GRANULAR], AALIYA: [REPO_AFROBEAT, REPO_JAZZ_CHOPIN], CHEN: [REPO_MICROTONAL, REPO_FILM_SCORE], FATOU: [REPO_DRUM_MACHINE, REPO_POLYRHYTHM], PIERRE: [REPO_CHANSON], BACH: [REPO_WTC, REPO_GOLDBERG], CHOPIN: [REPO_NOCTURNES], SCOTT_JOPLIN: [REPO_MAPLE_LEAF], KEVIN_MACLEOD: [REPO_CIN_STRINGS], KAI_ENGEL: [REPO_KAI_AMBIENT], } all_user_profiles = list(USERS) + list(COMPOSER_USERS) for uid, uname, _bio in all_user_profiles: p = PROFILE_DATA.get(uid, {}) db.add(MusehubProfile( user_id=uid, username=uname, display_name=str(p["display_name"]) if p.get("display_name") else uname, bio=str(p["bio"]) if p.get("bio") else _bio, avatar_url=f"https://api.dicebear.com/7.x/avataaars/svg?seed={uname}", location=str(p["location"]) if p.get("location") else None, website_url=str(p["website_url"]) if p.get("website_url") else None, twitter_handle=str(p["twitter_handle"]) if p.get("twitter_handle") else None, is_verified=bool(p.get("is_verified", False)), cc_license=str(p["cc_license"]) if p.get("cc_license") else None, pinned_repo_ids=_PROFILE_PINS.get(uid, []), )) verified_count = sum(1 for uid, _, __ in all_user_profiles if PROFILE_DATA.get(uid, {}).get("is_verified")) print(f" ✅ Profiles: {len(all_user_profiles)} users ({len(USERS)} community + {len(COMPOSER_USERS)} composer/archive, {verified_count} verified CC)") # ── 2. Repos ────────────────────────────────────────────────── all_repos = list(REPOS) + list(GENRE_REPOS) for r in all_repos: db.add(MusehubRepo( repo_id=r["repo_id"], name=r["name"], owner=r["owner"], slug=r["slug"], owner_user_id=r["owner_user_id"], visibility=r["visibility"], description=r["description"], tags=r["tags"], key_signature=r["key_signature"], tempo_bpm=r["tempo_bpm"], created_at=_now(days=r["days_ago"]), )) print(f" ✅ Repos: {len(all_repos)} ({len(REPOS)} original + {len(GENRE_REPOS)} genre archive)") await db.flush() # ── 3. Commits + Branches ───────────────────────────────────── all_commits: dict[str, list[dict[str, Any]]] = {} total_commits = 0 for r in all_repos: repo_id = r["repo_id"] rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") n = COMMIT_COUNTS.get(repo_id, 20) commits = _make_commits(repo_id, rkey, n) all_commits[repo_id] = commits total_commits += len(commits) for c in commits: db.add(MusehubCommit(**c)) # main branch always points to HEAD db.add(MusehubBranch(repo_id=repo_id, name="main", head_commit_id=commits[-1]["commit_id"])) if repo_id in GENRE_REPO_BRANCHES: # Genre archive repos: use specific named branches for branch_name, offset in GENRE_REPO_BRANCHES[repo_id]: idx = max(0, len(commits) - 1 - offset) db.add(MusehubBranch( repo_id=repo_id, name=branch_name, head_commit_id=commits[idx]["commit_id"], )) else: # Original repos: generic feature branches if len(commits) > 10: db.add(MusehubBranch(repo_id=repo_id, name="feat/develop", head_commit_id=commits[-4]["commit_id"])) if len(commits) > 20: db.add(MusehubBranch(repo_id=repo_id, name="experiment/alternate-harmony", head_commit_id=commits[-8]["commit_id"])) print(f" ✅ Commits: {total_commits} across {len(all_repos)} repos") await db.flush() # ── 4. Objects (track breakdown bar) ────────────────────────── obj_count = 0 for r in all_repos: repo_id = r["repo_id"] rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") tracks = REPO_TRACKS.get(rkey, REPO_TRACKS["neo-soul"]) commits = all_commits.get(repo_id, []) if not commits: continue # Attach objects to the last 3 commits for commit in commits[-3:]: cid = commit["commit_id"] for role, path in tracks: obj_id = f"sha256:{_sha(f'{cid}-{path}')}" db.add(MusehubObject( object_id=obj_id, repo_id=repo_id, path=path, size_bytes=len(path) * 1024, disk_path=f"/app/objects/{repo_id}/{obj_id[7:15]}.mid", created_at=commit["timestamp"], )) obj_count += 1 print(f" ✅ Objects: {obj_count} track files") await db.flush() # ── 4b. Labels (scoped to each repo — seeded before issues/PRs) ─────────── _LABEL_DEFS: list[tuple[str, str, str]] = [ # (name, color, description) ("bug", "#d73a4a", "Something isn't working correctly"), ("enhancement", "#a2eeef", "New feature or improvement request"), ("documentation","#0075ca", "Documentation update or correction"), ("question", "#d876e3", "Further information requested"), ("wontfix", "#ffffff", "This will not be addressed"), ("good first issue", "#7057ff", "Good for newcomers to the project"), ("help wanted", "#008672", "Extra attention needed"), ("in progress", "#e4e669", "Currently being worked on"), ("blocked", "#e11d48", "Blocked by another issue or dependency"), ("harmony", "#fbbf24", "Harmonic or tonal issue"), ("timing", "#6366f1", "Timing, groove or quantization issue"), ("mixing", "#10b981", "Mix balance, levels or EQ"), ("arrangement", "#f97316", "Arrangement or structure feedback"), ] # Structure: repo_id → {label_name → label_id} label_id_map: dict[str, dict[str, str]] = {} label_count = 0 for r in all_repos: repo_id = r["repo_id"] label_id_map[repo_id] = {} for lname, lcolor, ldesc in _LABEL_DEFS: lid = _uid(f"label-{repo_id}-{lname}") db.add(MusehubLabel( id=lid, repo_id=repo_id, name=lname, color=lcolor, description=ldesc, created_at=_now(days=r["days_ago"]), )) label_id_map[repo_id][lname] = lid label_count += 1 print(f" ✅ Labels: {label_count} ({len(_LABEL_DEFS)} per repo × {len(all_repos)} repos)") await db.flush() # ── 5a. Milestones (seed before issues so milestone_id can be referenced) ── # Structure: repo_id → {milestone_n → milestone_id} milestone_id_map: dict[str, dict[int, str]] = {} milestone_count = 0 for r in REPOS: repo_id = r["repo_id"] rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") ms_list = MILESTONE_TEMPLATES.get(rkey, []) if not ms_list: continue milestone_id_map[repo_id] = {} for ms in ms_list: mid = _uid(f"milestone-{repo_id}-{ms['n']}") due = _now(days=-ms["due_days"]) if ms.get("due_days") else None db.add(MusehubMilestone( milestone_id=mid, repo_id=repo_id, number=ms["n"], title=ms["title"], description=ms["description"], state=ms["state"], author=r["owner"], due_on=due, created_at=_now(days=r["days_ago"]), )) milestone_id_map[repo_id][ms["n"]] = mid milestone_count += 1 print(f" ✅ Milestones: {milestone_count}") await db.flush() # ── 5b. Issues (track IDs for comment and milestone-link seeding) ────────── issue_count = 0 # Structure: repo_id → {issue_n → issue_id} issue_id_map: dict[str, dict[int, str]] = {} for r in all_repos: repo_id = r["repo_id"] rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES) days_base = r["days_ago"] # Determine milestone assignment map for this repo: issue_n → milestone_n ms_assignments = MILESTONE_ISSUE_ASSIGNMENTS.get(rkey, {}) issue_to_ms: dict[int, int] = {} for ms_number, issue_ns in ms_assignments.items(): for iss_n in issue_ns: issue_to_ms[iss_n] = ms_number issue_id_map[repo_id] = {} for iss in issue_list: iid = _uid(f"issue-{repo_id}-{iss['n']}") assigned_ms_n: int | None = issue_to_ms.get(iss["n"]) ms_id: str | None = milestone_id_map.get(repo_id, {}).get(assigned_ms_n) if assigned_ms_n else None db.add(MusehubIssue( issue_id=iid, repo_id=repo_id, number=iss["n"], state=iss["state"], title=iss["title"], body=iss["body"], labels=iss["labels"], author=r["owner"], milestone_id=ms_id, created_at=_now(days=days_base - iss["n"] * 2), )) issue_id_map[repo_id][iss["n"]] = iid # Also populate MusehubIssueMilestone join table for repos with milestones if ms_id: db.add(MusehubIssueMilestone(issue_id=iid, milestone_id=ms_id)) issue_count += 1 print(f" ✅ Issues: {issue_count}") await db.flush() # ── 5b-ii. Issue labels (many-to-many join) ──────────────────────────────── _ISSUE_LABEL_PICKS: list[list[str]] = [ # Cycling pattern of label combos assigned to issues by index ["bug"], ["enhancement"], ["bug", "in progress"], ["question"], ["harmony"], ["timing"], ["mixing"], ["arrangement"], ["enhancement", "help wanted"], ["bug", "blocked"], ["documentation"], ["good first issue"], ["wontfix"], ["enhancement", "in progress"], ["harmony", "help wanted"], ] issue_label_count = 0 for r in all_repos: repo_id = r["repo_id"] rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES) repo_labels = label_id_map.get(repo_id, {}) for iss in issue_list: il_iid: str | None = issue_id_map.get(repo_id, {}).get(iss["n"]) if not il_iid: continue picks = _ISSUE_LABEL_PICKS[iss["n"] % len(_ISSUE_LABEL_PICKS)] for lname in picks: il_lid: str | None = repo_labels.get(lname) if il_lid: db.add(MusehubIssueLabel(issue_id=il_iid, label_id=il_lid)) issue_label_count += 1 print(f" ✅ Issue labels: {issue_label_count}") await db.flush() # ── 5c. Issue comments (5-10 per issue, with @mentions and code blocks) ──── issue_comment_count = 0 users_list = [u[1] for u in USERS] for r in REPOS[:10]: # Comments on all non-fork repos repo_id = r["repo_id"] rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") issue_list = ISSUE_TEMPLATES.get(rkey, GENERIC_ISSUES) for iss in issue_list: iss_iid = issue_id_map.get(repo_id, {}).get(iss["n"]) if not iss_iid: continue # 5-10 comments per issue (varies by issue number parity) n_comments = 5 + (iss["n"] % 6) iss_cmt_parent: str | None = None for j in range(n_comments): cmt_seed = hash(repo_id + str(iss["n"]) + str(j)) % 1000 body = _make_issue_comment_body(cmt_seed) musical_refs = _make_issue_musical_refs(body) author_idx = (abs(hash(repo_id)) + iss["n"] + j) % len(users_list) author = users_list[author_idx] cid = _uid(f"iss-comment-{repo_id}-{iss['n']}-{j}") db.add(MusehubIssueComment( comment_id=cid, issue_id=iss_iid, repo_id=repo_id, author=author, body=body, parent_id=iss_cmt_parent if j > 0 and j % 3 == 0 else None, musical_refs=musical_refs, created_at=_now(days=r["days_ago"] - iss["n"] * 2, hours=j * 2), )) issue_comment_count += 1 # First comment becomes parent for threaded replies if j == 0: iss_cmt_parent = cid print(f" ✅ Issue comments: {issue_comment_count}") await db.flush() # ── 6. Pull Requests ────────────────────────────────────────── pr_count = 0 pr_ids: dict[str, list[str]] = {} for r in all_repos: repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) prs = _make_prs(repo_id, commits, r["owner"]) pr_ids[repo_id] = [p["pr_id"] for p in prs] for pr in prs: db.add(MusehubPullRequest(**pr)) pr_count += 1 print(f" ✅ Pull Requests: {pr_count}") await db.flush() # ── 6b. PR comments (3-8 per PR with target_type variety) ───────────────── PR_COMMENT_BODIES: list[tuple[str, str, str | None, float | None, float | None, int | None]] = [ # (body, target_type, target_track, beat_start, beat_end, note_pitch) ("General: this PR looks good overall. The groove change is an improvement.", "general", None, None, None, None), ("The `bass` track changes in this PR need review — the wah envelope is still too slow.", "track", "bass", None, None, None), ("This region (beats 16-24) sounds much better with the humanized timing.", "region", "keys", 16.0, 24.0, None), ("The C#4 (MIDI 61) note in the Rhodes feels misplaced — should be D4 for the chord.", "note", "keys", None, None, 61), ("Great improvement in the horns section. The harmony is now correct.", "track", "horns", None, None, None), ("The beat 1-8 region on the bass now locks properly with the kick.", "region", "bass", 1.0, 8.0, None), ("The G3 (MIDI 55) in bar 7 creates an unwanted clash. Remove or lower octave.", "note", "strings", None, None, 55), ("Overall this PR solves the main issue. LGTM with minor nits.", "general", None, None, None, None), ("The `drums` ghost notes are much improved — much more human now.", "track", "drums", None, None, None), ("Beats 32-40 on the guitar feel slightly rushed. Did you check the quantize grid?", "region", "guitar", 32.0, 40.0, None), ] pr_comment_count = 0 for r in REPOS[:10]: repo_id = r["repo_id"] for pr_id_str in pr_ids.get(repo_id, []): # 3-8 comments per PR n_pr_comments = 3 + (abs(hash(pr_id_str)) % 6) parent_pr_cid: str | None = None for k in range(n_pr_comments): tmpl_idx = (abs(hash(pr_id_str)) + k) % len(PR_COMMENT_BODIES) body, ttype, ttrack, tbs, tbe, tnp = PR_COMMENT_BODIES[tmpl_idx] author_idx = (abs(hash(repo_id)) + k + 1) % len(users_list) pr_cid = _uid(f"pr-comment-{pr_id_str}-{k}") db.add(MusehubPRComment( comment_id=pr_cid, pr_id=pr_id_str, repo_id=repo_id, author=users_list[author_idx], body=body, target_type=ttype, target_track=ttrack, target_beat_start=tbs, target_beat_end=tbe, target_note_pitch=tnp, parent_comment_id=parent_pr_cid if k > 0 and k % 4 == 0 else None, created_at=_now(days=7 - k), )) pr_comment_count += 1 if k == 0: parent_pr_cid = pr_cid print(f" ✅ PR comments: {pr_comment_count}") await db.flush() # ── 6c. PR Reviews (reviewer assignment + approved/changes_requested) ────── _PR_REVIEW_STATES = ["approved", "approved", "changes_requested", "pending", "dismissed"] _PR_REVIEW_BODIES = [ "LGTM — the harmonic changes are solid and the voice-leading is now clean.", "Approved. The groove is much tighter after the timing adjustments.", "Changes requested: the bass still feels muddy in bars 9-16. Please reduce low-mids.", "Pending review — I'll listen through the changes this weekend.", "Approved with nits: the coda could be shorter, but the core change is correct.", "Changes requested: parallel fifths still present in bar 7 on the strings voice.", "LGTM — the new arrangement section is exactly what the composition needed.", ] pr_review_count = 0 for r in REPOS[:10]: repo_id = r["repo_id"] owner = r["owner"] for pr_id_str in pr_ids.get(repo_id, []): # 1-2 reviewers per PR, drawn from the community pool (not the PR author) n_reviewers = 1 + (abs(hash(pr_id_str)) % 2) reviewers_pool = [u for u in users_list if u != owner] for ri in range(n_reviewers): reviewer = reviewers_pool[(abs(hash(pr_id_str)) + ri) % len(reviewers_pool)] state = _PR_REVIEW_STATES[(abs(hash(pr_id_str)) + ri) % len(_PR_REVIEW_STATES)] body = _PR_REVIEW_BODIES[(abs(hash(pr_id_str)) + ri) % len(_PR_REVIEW_BODIES)] submitted = _now(days=5 - ri) if state != "pending" else None db.add(MusehubPRReview( id=_uid(f"pr-review-{pr_id_str}-{ri}"), pr_id=pr_id_str, reviewer_username=reviewer, state=state, body=body if state != "pending" else None, submitted_at=submitted, created_at=_now(days=7 - ri), )) pr_review_count += 1 print(f" ✅ PR reviews: {pr_review_count}") await db.flush() # ── 6d. PR Labels (label tags on pull requests) ──────────────────────────── _PR_LABEL_PICKS: list[list[str]] = [ ["enhancement"], ["bug"], ["enhancement", "in progress"], ["wontfix"], ] pr_label_count = 0 for r in all_repos: repo_id = r["repo_id"] repo_labels = label_id_map.get(repo_id, {}) for pi, pr_id_str in enumerate(pr_ids.get(repo_id, [])): picks = _PR_LABEL_PICKS[pi % len(_PR_LABEL_PICKS)] for lname in picks: prl_lid: str | None = repo_labels.get(lname) if prl_lid: db.add(MusehubPRLabel(pr_id=pr_id_str, label_id=prl_lid)) pr_label_count += 1 print(f" ✅ PR labels: {pr_label_count}") await db.flush() # ── 7. Releases ─────────────────────────────────────────────── release_count = 0 release_tags: dict[str, list[str]] = {} for r in all_repos: repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) releases = _make_releases(repo_id, commits, r["name"], r["owner"]) release_tags[repo_id] = [rel["tag"] for rel in releases] for rel in releases: db.add(MusehubRelease(**rel)) release_count += 1 print(f" ✅ Releases: {release_count}") await db.flush() # ── 7b. Release Assets (downloadable file attachments per release) ───────── _ASSET_TYPES: list[tuple[str, str, str, int]] = [ # (name_suffix, label, content_type, approx_size_bytes) ("-midi-bundle.zip", "MIDI Bundle", "application/zip", 2_400_000), ("-stereo-mix.mp3", "Stereo Mix", "audio/mpeg", 8_200_000), ("-stems.zip", "Stems Archive", "application/zip", 42_000_000), ("-score.pdf", "Score PDF", "application/pdf", 1_100_000), ("-metadata.json", "Metadata", "application/json", 18_000), ] # Only full releases (v1.0.0) get all 5 assets; earlier releases get 2. release_asset_count = 0 for r in all_repos: repo_id = r["repo_id"] tags = release_tags.get(repo_id, []) for ti, tag in enumerate(tags): # release_id is deterministic — matches what _make_releases sets rel_id = _uid(f"release-{repo_id}-{tag}") # Full release gets all asset types; earlier releases get 2 n_assets = len(_ASSET_TYPES) if ti == len(tags) - 1 else 2 base_slug = r["slug"] for ai, (sfx, label, ctype, base_size) in enumerate(_ASSET_TYPES[:n_assets]): dl_count = max(0, (50 - ti * 15) - ai * 5 + abs(hash(repo_id + tag)) % 20) db.add(MusehubReleaseAsset( asset_id=_uid(f"asset-{rel_id}-{ai}"), release_id=rel_id, repo_id=repo_id, name=f"{base_slug}-{tag}{sfx}", label=label, content_type=ctype, size=base_size + abs(hash(repo_id)) % 500_000, download_url=f"/releases/{repo_id}/{tag}{sfx}", download_count=dl_count, created_at=_now(days=max(1, 45 - ti * 20)), )) release_asset_count += 1 print(f" ✅ Release assets: {release_asset_count}") await db.flush() # ── 8. Sessions ─────────────────────────────────────────────── session_count = 0 session_ids: dict[str, list[str]] = {} for r in all_repos: repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) sessions = _make_sessions(repo_id, r["owner"], commits) session_ids[repo_id] = [s["session_id"] for s in sessions] for sess in sessions: db.add(MusehubSession(**sess)) session_count += 1 print(f" ✅ Sessions: {session_count}") await db.flush() # ── 8b. Collaborators (repo access beyond owner) ─────────────────────────── # Each active repo gets 1-3 collaborators from the community pool. # Collaborators have write permission; the most active repos get an admin. _COLLAB_CONFIGS: list[tuple[str, str, list[tuple[str, str]]]] = [ # (repo_id, owner_user_id, [(collab_user_id, permission)]) (REPO_NEO_SOUL, GABRIEL, [(MARCUS, "write"), (SOFIA, "write"), (AALIYA, "admin")]), (REPO_MODAL_JAZZ, GABRIEL, [(MARCUS, "write"), (CHEN, "write")]), (REPO_AMBIENT, SOFIA, [(YUKI, "write"), (PIERRE, "write"), (GABRIEL, "admin")]), (REPO_AFROBEAT, AALIYA, [(FATOU, "write"), (GABRIEL, "write")]), (REPO_MICROTONAL, CHEN, [(PIERRE, "write")]), (REPO_DRUM_MACHINE, FATOU, [(AALIYA, "write"), (MARCUS, "write")]), (REPO_CHANSON, PIERRE, [(SOFIA, "write")]), (REPO_GRANULAR, YUKI, [(SOFIA, "write"), (CHEN, "write")]), (REPO_FUNK_SUITE, MARCUS, [(GABRIEL, "write"), (AALIYA, "admin")]), (REPO_JAZZ_TRIO, MARCUS, [(GABRIEL, "write")]), (REPO_NEO_BAROQUE, GABRIEL, [(PIERRE, "write"), (CHEN, "write")]), (REPO_JAZZ_CHOPIN, AALIYA, [(GABRIEL, "write"), (MARCUS, "write")]), (REPO_COMMUNITY, GABRIEL, [(SOFIA, "admin"), (MARCUS, "write"), (YUKI, "write"), (AALIYA, "write"), (CHEN, "write"), (FATOU, "write"), (PIERRE, "write")]), ] collab_count = 0 for repo_id, owner_uid, collab_list in _COLLAB_CONFIGS: for collab_uid, perm in collab_list: accepted = _now(days=abs(hash(collab_uid + repo_id)) % 20 + 1) db.add(MusehubCollaborator( id=_uid(f"collab-{repo_id}-{collab_uid}"), repo_id=repo_id, user_id=collab_uid, permission=perm, invited_by=owner_uid, invited_at=_now(days=abs(hash(collab_uid + repo_id)) % 20 + 3), accepted_at=accepted, )) collab_count += 1 print(f" ✅ Collaborators: {collab_count}") await db.flush() # ── 8c. Stash (shelved in-progress work per user/repo) ──────────────────── _STASH_MESSAGES: list[str] = [ "WIP: Rhodes chord voicings — not ready to commit", "Experiment: tritone sub on IV chord — might revert", "Sketching counter-melody — needs more work", "Bass line draft — 3-against-4 groove not locked yet", "Drum fills experiment — comparing two approaches", "Harmony sketch: parallel 10ths — maybe too classical?", "Tempo map idea: ritardando at bar 36 — not sure yet", ] _STASH_CONFIGS: list[tuple[str, str, str]] = [ # (repo_id, user_id, branch) (REPO_NEO_SOUL, GABRIEL, "feat/counter-melody"), (REPO_AMBIENT, SOFIA, "experiment/drone-layer"), (REPO_AFROBEAT, AALIYA, "feat/brass-section"), (REPO_FUNK_SUITE, MARCUS, "experiment/fretless-bass"), (REPO_MICROTONAL, CHEN, "feat/spectral-harmony"), (REPO_GRANULAR, YUKI, "experiment/formant-filter"), (REPO_CHANSON, PIERRE, "feat/coda-extension"), (REPO_COMMUNITY, GABRIEL, "collab/all-genre-finale"), (REPO_MODAL_JAZZ, GABRIEL, "feat/mcoy-voicings"), (REPO_NEO_BAROQUE, GABRIEL, "experiment/fugue-development"), ] stash_count = 0 stash_entry_count = 0 for si, (repo_id, user_id, branch) in enumerate(_STASH_CONFIGS): stash_id = _uid(f"stash-{repo_id}-{user_id}-{si}") msg = _STASH_MESSAGES[si % len(_STASH_MESSAGES)] is_applied = si % 5 == 0 # Every 5th stash has been popped applied_at = _now(days=1) if is_applied else None db.add(MusehubStash( id=stash_id, repo_id=repo_id, user_id=user_id, branch=branch, message=msg, is_applied=is_applied, created_at=_now(days=si + 2), applied_at=applied_at, )) stash_count += 1 # 2-4 MIDI file entries per stash rkey = REPO_KEY_MAP.get(repo_id, "neo-soul") stash_tracks = REPO_TRACKS.get(rkey, REPO_TRACKS["neo-soul"]) n_entries = 2 + (si % 3) for ei, (role, fpath) in enumerate(stash_tracks[:n_entries]): obj_id = f"sha256:{_sha(f'stash-obj-{stash_id}-{role}')}" db.add(MusehubStashEntry( id=_uid(f"stash-entry-{stash_id}-{ei}"), stash_id=stash_id, path=f"tracks/{fpath}", object_id=obj_id, position=ei, )) stash_entry_count += 1 print(f" ✅ Stash: {stash_count} stashes, {stash_entry_count} entries") await db.flush() # ── 9. Stars (cross-repo) ───────────────────────────────────── # Every community user stars 5-10 repos; genre/composer archives also get # starred by community users who inspired or forked from them. 50+ total. star_pairs = [ # Community repos — broad community engagement (SOFIA, REPO_NEO_SOUL, 20), (MARCUS, REPO_NEO_SOUL, 18), (YUKI, REPO_NEO_SOUL, 15), (AALIYA, REPO_NEO_SOUL, 12), (CHEN, REPO_NEO_SOUL, 10), (FATOU, REPO_NEO_SOUL, 8), (PIERRE, REPO_NEO_SOUL, 6), (GABRIEL, REPO_AMBIENT, 14), (MARCUS, REPO_AMBIENT, 12), (YUKI, REPO_AMBIENT, 10), (AALIYA, REPO_AMBIENT, 9), (CHEN, REPO_AMBIENT, 7), (FATOU, REPO_AMBIENT, 5), (PIERRE, REPO_AMBIENT, 3), (GABRIEL, REPO_AFROBEAT, 8), (SOFIA, REPO_AFROBEAT, 7), (MARCUS, REPO_AFROBEAT, 6), (YUKI, REPO_AFROBEAT, 5), (CHEN, REPO_AFROBEAT, 4), (PIERRE, REPO_AFROBEAT, 3), (GABRIEL, REPO_FUNK_SUITE, 12), (SOFIA, REPO_FUNK_SUITE, 10), (AALIYA, REPO_FUNK_SUITE, 9), (CHEN, REPO_FUNK_SUITE, 7), (FATOU, REPO_FUNK_SUITE, 5), (GABRIEL, REPO_MODAL_JAZZ, 11), (SOFIA, REPO_MODAL_JAZZ, 8), (MARCUS, REPO_MODAL_JAZZ, 6), (AALIYA, REPO_MODAL_JAZZ, 4), (GABRIEL, REPO_JAZZ_TRIO, 9), (SOFIA, REPO_JAZZ_TRIO, 7), (AALIYA, REPO_JAZZ_TRIO, 5), (CHEN, REPO_JAZZ_TRIO, 3), (GABRIEL, REPO_DRUM_MACHINE, 6), (MARCUS, REPO_DRUM_MACHINE, 4), (GABRIEL, REPO_GRANULAR, 5), (MARCUS, REPO_GRANULAR, 3), (GABRIEL, REPO_CHANSON, 4), (SOFIA, REPO_CHANSON, 3), (GABRIEL, REPO_MICROTONAL, 3), (SOFIA, REPO_MICROTONAL, 2), # Genre/composer archive repos starred by community users who draw # inspiration from or fork into them (GABRIEL, REPO_GOLDBERG, 25), (SOFIA, REPO_GOLDBERG, 22), (MARCUS, REPO_GOLDBERG, 18), (AALIYA, REPO_GOLDBERG, 15), (CHEN, REPO_GOLDBERG, 12), (FATOU, REPO_GOLDBERG, 9), (PIERRE, REPO_GOLDBERG, 7), (YUKI, REPO_GOLDBERG, 5), (GABRIEL, REPO_WTC, 24), (SOFIA, REPO_WTC, 20), (CHEN, REPO_WTC, 16), (MARCUS, REPO_WTC, 13), (YUKI, REPO_WTC, 10), (PIERRE, REPO_WTC, 6), (GABRIEL, REPO_NOCTURNES, 22), (AALIYA, REPO_NOCTURNES, 18), (SOFIA, REPO_NOCTURNES, 15), (CHEN, REPO_NOCTURNES, 11), (MARCUS, REPO_NOCTURNES, 8), (YUKI, REPO_NOCTURNES, 5), (MARCUS, REPO_MAPLE_LEAF, 20), (GABRIEL, REPO_MAPLE_LEAF, 17), (AALIYA, REPO_MAPLE_LEAF, 13), (FATOU, REPO_MAPLE_LEAF, 9), (PIERRE, REPO_MAPLE_LEAF, 6), (CHEN, REPO_CIN_STRINGS, 19), (GABRIEL, REPO_CIN_STRINGS, 15), (SOFIA, REPO_CIN_STRINGS, 12), (YUKI, REPO_CIN_STRINGS, 8), (MARCUS, REPO_CIN_STRINGS, 5), # Community genre-fusion repos (created as forks of composer archives) (SOFIA, REPO_NEO_BAROQUE, 14), (MARCUS, REPO_NEO_BAROQUE, 11), (CHEN, REPO_NEO_BAROQUE, 8), (YUKI, REPO_NEO_BAROQUE, 5), (PIERRE, REPO_NEO_BAROQUE, 3), (GABRIEL, REPO_JAZZ_CHOPIN, 12), (MARCUS, REPO_JAZZ_CHOPIN, 9), (SOFIA, REPO_JAZZ_CHOPIN, 7), (CHEN, REPO_JAZZ_CHOPIN, 4), (GABRIEL, REPO_RAGTIME_EDM, 10), (AALIYA, REPO_RAGTIME_EDM, 8), (FATOU, REPO_RAGTIME_EDM, 6), (YUKI, REPO_RAGTIME_EDM, 4), (GABRIEL, REPO_FILM_SCORE, 9), (SOFIA, REPO_FILM_SCORE, 7), (MARCUS, REPO_FILM_SCORE, 5), (AALIYA, REPO_FILM_SCORE, 3), (GABRIEL, REPO_COMMUNITY, 8), (SOFIA, REPO_COMMUNITY, 6), (MARCUS, REPO_COMMUNITY, 5), (AALIYA, REPO_COMMUNITY, 4), (CHEN, REPO_COMMUNITY, 3), (FATOU, REPO_COMMUNITY, 2), (PIERRE, REPO_COMMUNITY, 1), ] for user_id, repo_id, days in star_pairs: db.add(MusehubStar(repo_id=repo_id, user_id=user_id, created_at=_now(days=days))) print(f" ✅ Stars: {len(star_pairs)}") await db.flush() # ── 10. Comments ───────────────────────────────────────────── COMMENT_BODIES = [ "This groove is incredible — the 3-against-4 polyrhythm is exactly what this track needed.", "Love the Rhodes voicings here. Upper-structure triads give it that sophisticated neo-soul feel.", "The humanization really helped. Feels much more like a live performance now.", "I think the bridge needs more harmonic tension. The IV-I resolution is too settled.", "That trumpet counter-melody is stunning. It perfectly answers the Rhodes line.", "Could we push the bass a bit more? It's sitting a little behind the kick drum.", "The string pizzicato in the verse is a beautiful subtle touch.", "I'm not sure about the guitar scratch — feels a bit busy with the Rhodes.", "This is really coming together. The dynamic arc from intro to chorus is perfect.", "The call-and-response between horns and strings is very Quincy Jones.", ] comment_count = 0 for r in REPOS[:6]: # Comments on first 6 repos repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) if not commits: continue # Comments on latest 3 commits for i, commit in enumerate(commits[-3:]): for j in range(3): body = COMMENT_BODIES[(i * 3 + j) % len(COMMENT_BODIES)] author_ids = [GABRIEL, SOFIA, MARCUS, YUKI, AALIYA, CHEN, FATOU, PIERRE] author = [u[1] for u in USERS if u[0] == author_ids[(i + j + hash(repo_id)) % len(author_ids)]][0] comment_id = _uid(f"comment-{repo_id}-{i}-{j}") db.add(MusehubComment( comment_id=comment_id, repo_id=repo_id, target_type="commit", target_id=commit["commit_id"], author=author, body=body, parent_id=None, created_at=_now(days=2, hours=i * 3 + j), )) comment_count += 1 # Add a reply to first comment on each commit if j == 0: reply_author = [u[1] for u in USERS if u[1] != author][0] db.add(MusehubComment( comment_id=_uid(f"reply-{repo_id}-{i}-{j}"), repo_id=repo_id, target_type="commit", target_id=commit["commit_id"], author=reply_author, body="Totally agree — this part really elevates the whole track.", parent_id=comment_id, created_at=_now(days=1, hours=i * 2), )) comment_count += 1 print(f" ✅ Comments: {comment_count}") await db.flush() # ── 11. Reactions ───────────────────────────────────────────── # All 8 emoji types from the spec: 👍❤️🎵🔥🎹👏🤔😢. 200+ total across # both community and genre-archive repos. EMOJIS = ["👍", "❤️", "🎵", "🔥", "🎹", "👏", "🤔", "😢"] reaction_count = 0 all_community_users = [GABRIEL, SOFIA, MARCUS, YUKI, AALIYA, CHEN, FATOU, PIERRE] # Community repos — all 8 users react to last 5 commits on first 6 repos for r in REPOS[:6]: repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) if not commits: continue for commit in commits[-5:]: for i, uid in enumerate(all_community_users): emoji = EMOJIS[i % len(EMOJIS)] try: db.add(MusehubReaction( reaction_id=_uid(f"reaction-{repo_id}-{commit['commit_id'][:8]}-{uid}"), repo_id=repo_id, target_type="commit", target_id=commit["commit_id"], user_id=uid, emoji=emoji, created_at=_now(days=1), )) reaction_count += 1 except Exception: pass # Genre-archive repos — community users react to last 3 commits, rotating # through all emoji types to ensure full coverage genre_reaction_repos = [ REPO_GOLDBERG, REPO_WTC, REPO_NOCTURNES, REPO_MAPLE_LEAF, REPO_CIN_STRINGS, ] for repo_id in genre_reaction_repos: commits = all_commits.get(repo_id, []) if not commits: continue for ci, commit in enumerate(commits[-3:]): for ui, uid in enumerate(all_community_users): emoji = EMOJIS[(ci * len(all_community_users) + ui) % len(EMOJIS)] try: db.add(MusehubReaction( reaction_id=_uid(f"reaction-g-{repo_id[:12]}-{commit['commit_id'][:8]}-{uid}"), repo_id=repo_id, target_type="commit", target_id=commit["commit_id"], user_id=uid, emoji=emoji, created_at=_now(days=2), )) reaction_count += 1 except Exception: pass print(f" ✅ Reactions: {reaction_count}") await db.flush() # ── 12. Follows (social graph) ──────────────────────────────── # Rich bidirectional follow graph: gabriel↔sofia, marcus↔fatou, # yuki↔chen, aaliya↔pierre, plus composer-to-community asymmetric follows. # 60+ total, preserving all prior pairs. follow_pairs = [ # Original follows — gabriel is the hub (everyone follows him) (SOFIA, GABRIEL), (MARCUS, GABRIEL), (YUKI, GABRIEL), (AALIYA, GABRIEL), (CHEN, GABRIEL), (FATOU, GABRIEL), (PIERRE, GABRIEL), # Bidirectional close collaborator pairs (symmetric) (GABRIEL, SOFIA), (SOFIA, GABRIEL), # already added above, deduped by _uid (MARCUS, FATOU), (FATOU, MARCUS), (YUKI, CHEN), (CHEN, YUKI), (AALIYA, PIERRE), (PIERRE, AALIYA), # Cross-community follows (GABRIEL, MARCUS), (SOFIA, MARCUS), (AALIYA, MARCUS), (GABRIEL, YUKI), (SOFIA, YUKI), (GABRIEL, AALIYA), (MARCUS, AALIYA), (GABRIEL, CHEN), (GABRIEL, FATOU), (AALIYA, FATOU), (GABRIEL, PIERRE), (SOFIA, PIERRE), (MARCUS, SOFIA), (PIERRE, SOFIA), (MARCUS, YUKI), (FATOU, YUKI), (PIERRE, MARCUS), (YUKI, MARCUS), (CHEN, MARCUS), (FATOU, SOFIA), (YUKI, AALIYA), (CHEN, AALIYA), (FATOU, AALIYA), (MARCUS, CHEN), (AALIYA, CHEN), (PIERRE, CHEN), (SOFIA, FATOU), (CHEN, FATOU), (PIERRE, FATOU), (YUKI, PIERRE), (MARCUS, PIERRE), (CHEN, PIERRE), (SOFIA, CHEN), (AALIYA, YUKI), (FATOU, CHEN), # Community users follow composer archive maintainers (GABRIEL, BACH), (SOFIA, BACH), (MARCUS, BACH), (CHEN, BACH), (AALIYA, BACH), (YUKI, BACH), (GABRIEL, CHOPIN), (AALIYA, CHOPIN), (SOFIA, CHOPIN), (MARCUS, SCOTT_JOPLIN), (GABRIEL, SCOTT_JOPLIN), (FATOU, SCOTT_JOPLIN), (CHEN, KEVIN_MACLEOD), (GABRIEL, KEVIN_MACLEOD), ] # Deduplicate pairs so _uid collisions never cause constraint errors seen_follows: set[tuple[str, str]] = set() deduped_follows = [] for pair in follow_pairs: if pair not in seen_follows: seen_follows.add(pair) deduped_follows.append(pair) for follower, followee in deduped_follows: db.add(MusehubFollow( follow_id=_uid(f"follow-{follower}-{followee}"), follower_id=follower, followee_id=followee, created_at=_now(days=15), )) print(f" ✅ Follows: {len(deduped_follows)}") await db.flush() # ── 13. Watches ─────────────────────────────────────────────── # 60+ total: community users watch each other's repos and the composer # archive repos they draw inspiration from. watch_pairs = [ # Community repo watches (GABRIEL, REPO_AMBIENT), (GABRIEL, REPO_AFROBEAT), (GABRIEL, REPO_FUNK_SUITE), (GABRIEL, REPO_JAZZ_TRIO), (GABRIEL, REPO_GRANULAR), (GABRIEL, REPO_CHANSON), (GABRIEL, REPO_MICROTONAL), (GABRIEL, REPO_DRUM_MACHINE), (SOFIA, REPO_NEO_SOUL), (SOFIA, REPO_FUNK_SUITE), (SOFIA, REPO_CHANSON), (SOFIA, REPO_MODAL_JAZZ), (SOFIA, REPO_AFROBEAT), (SOFIA, REPO_MICROTONAL), (MARCUS, REPO_NEO_SOUL), (MARCUS, REPO_AMBIENT), (MARCUS, REPO_AFROBEAT), (MARCUS, REPO_MODAL_JAZZ), (MARCUS, REPO_JAZZ_TRIO), (MARCUS, REPO_FUNK_SUITE), (YUKI, REPO_AMBIENT), (YUKI, REPO_GRANULAR), (YUKI, REPO_MICROTONAL), (YUKI, REPO_NEO_SOUL), (YUKI, REPO_DRUM_MACHINE), (AALIYA, REPO_NEO_SOUL), (AALIYA, REPO_AFROBEAT), (AALIYA, REPO_FUNK_SUITE), (AALIYA, REPO_MODAL_JAZZ), (AALIYA, REPO_JAZZ_TRIO), (AALIYA, REPO_CHANSON), (CHEN, REPO_MICROTONAL), (CHEN, REPO_AMBIENT), (CHEN, REPO_GRANULAR), (CHEN, REPO_MODAL_JAZZ), (CHEN, REPO_NEO_SOUL), (CHEN, REPO_DRUM_MACHINE), (FATOU, REPO_AFROBEAT), (FATOU, REPO_DRUM_MACHINE),(FATOU, REPO_FUNK_SUITE), (FATOU, REPO_NEO_SOUL), (FATOU, REPO_MODAL_JAZZ), (FATOU, REPO_GRANULAR), (PIERRE, REPO_CHANSON), (PIERRE, REPO_AMBIENT), (PIERRE, REPO_NEO_SOUL), (PIERRE, REPO_MODAL_JAZZ), (PIERRE, REPO_MICROTONAL), # Composer archive repo watches — community users watching source material (GABRIEL, REPO_GOLDBERG), (GABRIEL, REPO_WTC), (GABRIEL, REPO_NOCTURNES), (GABRIEL, REPO_MAPLE_LEAF), (GABRIEL, REPO_CIN_STRINGS), (SOFIA, REPO_GOLDBERG), (SOFIA, REPO_WTC), (SOFIA, REPO_NOCTURNES), (MARCUS, REPO_MAPLE_LEAF), (MARCUS, REPO_GOLDBERG), (MARCUS, REPO_WTC), (AALIYA, REPO_NOCTURNES), (AALIYA, REPO_MAPLE_LEAF), (CHEN, REPO_CIN_STRINGS), (CHEN, REPO_GOLDBERG), (YUKI, REPO_WTC), (FATOU, REPO_MAPLE_LEAF), (PIERRE, REPO_NOCTURNES), # Genre-fusion community repos (SOFIA, REPO_NEO_BAROQUE), (MARCUS, REPO_NEO_BAROQUE), (GABRIEL, REPO_JAZZ_CHOPIN), (MARCUS, REPO_RAGTIME_EDM), (GABRIEL, REPO_FILM_SCORE), (SOFIA, REPO_FILM_SCORE), (GABRIEL, REPO_COMMUNITY), (AALIYA, REPO_COMMUNITY), ] for user_id, repo_id in watch_pairs: db.add(MusehubWatch( watch_id=_uid(f"watch-{user_id}-{repo_id}"), user_id=user_id, repo_id=repo_id, created_at=_now(days=10), )) print(f" ✅ Watches: {len(watch_pairs)}") await db.flush() # ── 14. Notifications ───────────────────────────────────────── # 15-25 unread per user, all event types, all 8 users. 15-25 unread means # we create 20 notifications per user with the first 5 marked as read and # the remaining 15 unread — satisfying the "15-25 unread" spec. EVENT_TYPES = [ "comment", "pr_opened", "pr_merged", "issue_opened", "new_commit", "new_follower", "star", "fork", "release", "watch", ] NOTIFS_PER_USER = 20 # 5 read + 15 unread = 15 unread per user READ_THRESHOLD = 5 # first N are read all_repos_flat = [r["repo_id"] for r in (list(REPOS) + list(GENRE_REPOS))] notif_count = 0 for i, (uid, uname, _) in enumerate(USERS): for j in range(NOTIFS_PER_USER): actor_user = USERS[(i + j + 1) % len(USERS)] db.add(MusehubNotification( notif_id=_uid(f"notif2-{uid}-{j}"), recipient_id=uid, event_type=EVENT_TYPES[j % len(EVENT_TYPES)], repo_id=all_repos_flat[(i + j) % len(all_repos_flat)], actor=actor_user[1], payload={"message": f"Notification {j + 1} for {uname}"}, is_read=j < READ_THRESHOLD, created_at=_now(days=j), )) notif_count += 1 print(f" ✅ Notifications: {notif_count} ({NOTIFS_PER_USER - READ_THRESHOLD} unread per user)") await db.flush() # ── 15. Forks ───────────────────────────────────────────────── # Original community-to-community forks db.add(MusehubFork( fork_id=_uid("fork-neo-soul-marcus"), source_repo_id=REPO_NEO_SOUL, fork_repo_id=REPO_NEO_SOUL_FORK, forked_by="marcus", created_at=_now(days=10), )) db.add(MusehubFork( fork_id=_uid("fork-ambient-yuki"), source_repo_id=REPO_AMBIENT, fork_repo_id=REPO_AMBIENT_FORK, forked_by="yuki", created_at=_now(days=5), )) # Genre-archive → community forks: each community repo that remixes a # composer's archive is modelled as a fork of the canonical archive repo. # marcus/ragtime-edm ← scott_joplin/maple-leaf-rag db.add(MusehubFork( fork_id=_uid("fork-maple-leaf-marcus"), source_repo_id=REPO_MAPLE_LEAF, fork_repo_id=REPO_RAGTIME_EDM, forked_by="marcus", created_at=_now(days=30), )) # aaliya/jazz-chopin ← chopin/nocturnes db.add(MusehubFork( fork_id=_uid("fork-nocturnes-aaliya"), source_repo_id=REPO_NOCTURNES, fork_repo_id=REPO_JAZZ_CHOPIN, forked_by="aaliya", created_at=_now(days=28), )) # gabriel/neo-baroque ← bach/goldberg-variations db.add(MusehubFork( fork_id=_uid("fork-goldberg-gabriel-neobaroque"), source_repo_id=REPO_GOLDBERG, fork_repo_id=REPO_NEO_BAROQUE, forked_by="gabriel", created_at=_now(days=25), )) # chen/film-score ← kevin_macleod/cinematic-strings db.add(MusehubFork( fork_id=_uid("fork-cinstrings-chen"), source_repo_id=REPO_CIN_STRINGS, fork_repo_id=REPO_FILM_SCORE, forked_by="chen", created_at=_now(days=22), )) # gabriel/community-collab ← bach/goldberg-variations db.add(MusehubFork( fork_id=_uid("fork-goldberg-gabriel-community"), source_repo_id=REPO_GOLDBERG, fork_repo_id=REPO_COMMUNITY, forked_by="gabriel", created_at=_now(days=20), )) print(" ✅ Forks: 7") await db.flush() # ── 16. View events (analytics) ─────────────────────────────── # 1000+ total across all repos (community + genre archives). Each repo # receives 30 days of daily view fingerprints; active repos get up to 10 # unique viewers per day, quieter repos get fewer. view_count = 0 view_repos = list(REPOS) + list(GENRE_REPOS) for r in view_repos: repo_id = r["repo_id"] star_count = r.get("star_count", 5) for day_offset in range(30): date_str = (_now(days=day_offset)).strftime("%Y-%m-%d") viewers = max(star_count // 3 + 1, 3) # at least 3 unique viewers/day for v in range(min(viewers, 10)): try: db.add(MusehubViewEvent( view_id=_uid(f"view-{repo_id}-{day_offset}-{v}"), repo_id=repo_id, viewer_fingerprint=_sha(f"viewer-{repo_id}-{v}"), event_date=date_str, created_at=_now(days=day_offset), )) view_count += 1 except Exception: pass print(f" ✅ View events: {view_count}") await db.flush() # ── 17. Download events ─────────────────────────────────────── # 5-25 downloads per release tag across all repos. Using release_tags # (built in step 7) so every release gets at least 5 unique downloaders. all_user_ids = [u[0] for u in USERS] dl_count = 0 dl_repos = list(REPOS) + list(GENRE_REPOS) for r in dl_repos: repo_id = r["repo_id"] tags = release_tags.get(repo_id, ["main"]) if not tags: tags = ["main"] for ti, tag in enumerate(tags): # 5-15 downloads per release depending on tag index (older = more) n_downloads = max(5, 15 - ti * 2) for i in range(n_downloads): db.add(MusehubDownloadEvent( dl_id=_uid(f"dl2-{repo_id}-{tag}-{i}"), repo_id=repo_id, ref=tag, downloader_id=all_user_ids[i % len(all_user_ids)], created_at=_now(days=ti * 7 + i), )) dl_count += 1 print(f" ✅ Download events: {dl_count}") # ── 17b. Webhooks + deliveries (1-3/repo, 10-20 deliveries each) ────────── WEBHOOK_CONFIGS: list[tuple[str, list[str]]] = [ # (url_suffix, events) ("push", ["push"]), ("pr", ["pull_request", "push"]), ("release", ["release", "push", "pull_request"]), ] # Delivery outcome patterns: (response_status, success) # Mix of 200 OK, 500 server error, and 0 timeout DELIVERY_OUTCOMES: list[tuple[int, bool, str]] = [ (200, True, '{"status": "ok"}'), (200, True, '{"accepted": true}'), (200, True, '{"queued": true}'), (500, False, '{"error": "Internal Server Error"}'), (500, False, '{"error": "Service unavailable"}'), (0, False, ""), # timeout — no response (200, True, '{"ok": 1}'), (404, False, '{"error": "Not Found"}'), (200, True, '{"received": true}'), (0, False, ""), # timeout ] WEBHOOK_EVENT_TYPES = ["push", "pull_request", "release", "issue", "comment"] webhook_count = 0 delivery_count = 0 for r in REPOS[:10]: repo_id = r["repo_id"] # 1-3 webhooks per repo based on repo index repo_idx = REPOS.index(r) n_webhooks = 1 + (repo_idx % 3) for wh_i in range(n_webhooks): url_suffix, events = WEBHOOK_CONFIGS[wh_i % len(WEBHOOK_CONFIGS)] wh_id = _uid(f"webhook-{repo_id}-{wh_i}") db.add(MusehubWebhook( webhook_id=wh_id, repo_id=repo_id, url=f"https://hooks.example.com/{r['owner']}/{r['slug']}/{url_suffix}", events=events, secret=_sha(f"secret-{repo_id}-{wh_i}")[:32], active=True, created_at=_now(days=r["days_ago"] - 5), )) webhook_count += 1 # 10-20 deliveries per webhook n_deliveries = 10 + (abs(hash(wh_id)) % 11) for d_i in range(n_deliveries): outcome_idx = (abs(hash(wh_id)) + d_i) % len(DELIVERY_OUTCOMES) status, success, resp_body = DELIVERY_OUTCOMES[outcome_idx] evt = WEBHOOK_EVENT_TYPES[d_i % len(WEBHOOK_EVENT_TYPES)] db.add(MusehubWebhookDelivery( delivery_id=_uid(f"wh-delivery-{wh_id}-{d_i}"), webhook_id=wh_id, event_type=evt, payload=f'{{"event": "{evt}", "repo": "{repo_id}", "seq": {d_i}}}', attempt=1 + (d_i % 3), success=success, response_status=status, response_body=resp_body, delivered_at=_now(days=r["days_ago"] - d_i, hours=d_i % 24), )) delivery_count += 1 print(f" ✅ Webhooks: {webhook_count} Deliveries: {delivery_count}") await db.flush() # ── 17c. Render Jobs (async audio render pipeline) ───────────────────────── # Creates completed, processing, and failed render jobs across repos. # Each job ties a commit to a set of generated MP3/piano-roll artifact IDs. _RENDER_STATUSES = ["completed", "completed", "completed", "processing", "failed"] render_job_count = 0 for r in all_repos[:16]: # Community + most popular genre archive repos repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) if not commits: continue # 2-3 render jobs per repo (latest commits) target_commits = commits[-3:] for ji, c in enumerate(target_commits): rj_cid: str = c["commit_id"] rj_status: str = _RENDER_STATUSES[(abs(hash(repo_id)) + ji) % len(_RENDER_STATUSES)] err_msg = "Storpheus timeout after 30s" if rj_status == "failed" else None mp3_ids = ( [f"sha256:{_sha(f'mp3-{rj_cid}-{i}')}" for i in range(3)] if rj_status == "completed" else [] ) img_ids = ( [f"sha256:{_sha(f'img-{rj_cid}-{i}')}" for i in range(2)] if rj_status == "completed" else [] ) try: db.add(MusehubRenderJob( render_job_id=_uid(f"rjob-{repo_id}-{rj_cid[:12]}"), repo_id=repo_id, commit_id=rj_cid, status=rj_status, error_message=err_msg, midi_count=len(REPO_TRACKS.get(REPO_KEY_MAP.get(repo_id, "neo-soul"), REPO_TRACKS["neo-soul"])), mp3_object_ids=mp3_ids, image_object_ids=img_ids, created_at=_now(days=ji + 1), updated_at=_now(days=ji), )) render_job_count += 1 except Exception: pass # unique constraint on (repo_id, commit_id) — skip dupes print(f" ✅ Render jobs: {render_job_count}") await db.flush() # ── 17d. Activity Event Stream (musehub_events) ──────────────────────────── # Captures the most recent activity on each repo: push, pr_open, pr_merge, # issue_open, issue_close, release, tag, session, fork, star events. _EVENT_TEMPLATES: list[tuple[str, str]] = [ ("push", "pushed {n} commits to {branch}"), ("push", "force-pushed to {branch} (rebase)"), ("pr_open", "opened pull request: {title}"), ("pr_merge", "merged pull request into {branch}"), ("pr_close", "closed pull request without merging"), ("issue_open", "opened issue: {title}"), ("issue_close", "closed issue #{n}"), ("release", "published release {tag}"), ("tag", "created tag {tag} at {branch}"), ("session_start", "started recording session"), ("session_end", "ended recording session — {n} commits"), ("fork", "forked this repo"), ("star", "starred this repo"), ("branch_create", "created branch {branch}"), ] _EVENT_ACTORS_POOL = list(ALL_CONTRIBUTORS) event_count = 0 for r in all_repos: repo_id = r["repo_id"] commits = all_commits.get(repo_id, []) owner = r["owner"] actor_pool = [owner] * 3 + _EVENT_ACTORS_POOL # Weight owner higher # Generate 8-15 events per repo spread over the last 60 days n_events = 8 + abs(hash(repo_id)) % 8 for ei in range(n_events): tmpl_type, tmpl_body = _EVENT_TEMPLATES[ei % len(_EVENT_TEMPLATES)] actor = actor_pool[(abs(hash(repo_id)) + ei) % len(actor_pool)] n_val = (ei % 10) + 1 branch = "main" if ei % 3 == 0 else f"feat/{r['slug'][:20]}-{ei}" tag = f"v{ei // 3 + 1}.0.{'0' if ei % 2 == 0 else '1'}" description = ( tmpl_body .replace("{n}", str(n_val)) .replace("{branch}", branch) .replace("{title}", f"Issue/PR title for event {ei}") .replace("{tag}", tag) ) meta: dict[str, object] = { "actor": actor, "branch": branch, "commit_count": n_val, } if commits and ei < len(commits): meta["commit_id"] = commits[ei]["commit_id"] db.add(MusehubEvent( event_id=_uid(f"event-{repo_id}-{ei}"), repo_id=repo_id, event_type=tmpl_type, actor=actor, description=description, event_metadata=meta, created_at=_now(days=60 - ei * 4, hours=ei * 3 % 24), )) event_count += 1 print(f" ✅ Events: {event_count}") await db.flush() # ── 18. Muse variations, phrases, note changes ──────────────── # Collect stable commit hashes from the two most active repos as base # state IDs. muse_variations.base_state_id links a variation to the DAW # snapshot it was proposed against — we reuse musehub commit hashes as a # realistic stand-in for DAW project state hashes. neo_commits = all_commits.get(REPO_NEO_SOUL, []) jazz_commits = all_commits.get(REPO_MODAL_JAZZ, []) neo_hashes = [c["commit_id"] for c in neo_commits[-15:]] or [_sha("nb-fallback")] jazz_hashes = [c["commit_id"] for c in jazz_commits[-15:]] or [_sha("cc-fallback")] var_nb, phrase_nb, nc_nb = _make_variation_section( project_id=PROJECT_NEO_BAROQUE, intents=VARIATION_INTENTS_NEO_BAROQUE, track_ids=TRACK_IDS_NEO_BAROQUE, region_ids=REGION_IDS_NEO_BAROQUE, base_commit_hashes=neo_hashes, seed_prefix="nb", ) var_cc, phrase_cc, nc_cc = _make_variation_section( project_id=PROJECT_COMMUNITY_COLLAB, intents=VARIATION_INTENTS_COMMUNITY_COLLAB, track_ids=TRACK_IDS_COMMUNITY, region_ids=REGION_IDS_COMMUNITY, base_commit_hashes=jazz_hashes, seed_prefix="cc", ) all_variations = var_nb + var_cc all_phrases = phrase_nb + phrase_cc all_note_changes = nc_nb + nc_cc for var in all_variations: db.add(var) await db.flush() for ph in all_phrases: db.add(ph) await db.flush() for nc in all_note_changes: db.add(nc) await db.flush() print(f" ✅ Variations: {len(all_variations)} ({len(var_nb)} neo-baroque, {len(var_cc)} community-collab)") print(f" ✅ Phrases: {len(all_phrases)}") print(f" ✅ Note changes: {len(all_note_changes)}") # ── 19. Muse VCS — muse_objects, muse_snapshots, muse_commits, muse_tags ─ # # Inserts content-addressed MIDI blobs, snapshot manifests, a proper DAG # of Muse commits (including merge commits), and the full tag taxonomy. # # Insertion order respects FK constraints: # muse_objects → muse_snapshots → muse_commits → muse_tags # muse_obj_count = 0 muse_snap_count = 0 muse_commit_count = 0 muse_tag_count = 0 # Running objects pool so the same content can be deduplicated across # snapshots (object_ids that haven't changed reuse the same sha256). # Structure: repo_id → {filename: object_id} _prev_objects: dict[str, dict[str, str]] = {} for r in REPOS: repo_id = r["repo_id"] hub_commits = all_commits.get(repo_id, []) if not hub_commits: continue track_files = MUSE_VCS_FILES.get(repo_id, MUSE_VCS_FILES[REPO_AMBIENT]) meta = MUSE_COMMIT_META.get(repo_id, MUSE_COMMIT_META[REPO_AMBIENT]) is_rich = repo_id in MUSE_RICH_TAG_REPOS prev_objects: dict[str, str] = {} # filename → object_id for this repo muse_commit_ids: list[str] = [] # ordered muse commit_ids for this repo for i, hub_c in enumerate(hub_commits): snap_seed = f"snap-muse-{repo_id}-{i}" committed_at = hub_c["timestamp"] # Build this commit's object set. # Every commit, ~2 files "change" (get fresh object_ids). # The rest reuse from the previous commit — simulating deduplication. changed_indices = {i % len(track_files), (i + 2) % len(track_files)} commit_objects: dict[str, str] = {} for fi, (fname, base_size) in enumerate(track_files): if fi in changed_indices or fname not in prev_objects: # New or modified file → fresh content-addressed blob. obj_id = _sha(f"midi-{repo_id}-{fname}-v{i}") size = base_size + (i * 128) % 4096 await db.execute( text( "INSERT INTO muse_objects (object_id, size_bytes, created_at)" " VALUES (:oid, :sz, :ca)" " ON CONFLICT (object_id) DO NOTHING" ), {"oid": obj_id, "sz": size, "ca": committed_at}, ) muse_obj_count += 1 else: # Unchanged file → reuse previous object_id (deduplication). obj_id = prev_objects[fname] commit_objects[fname] = obj_id prev_objects = commit_objects # Snapshot — manifest maps track paths to object_ids. snapshot_id = _sha(snap_seed) manifest: dict[str, str] = {f"tracks/{fname}": oid for fname, oid in commit_objects.items()} await db.execute( text( "INSERT INTO muse_snapshots (snapshot_id, manifest, created_at)" " VALUES (:sid, :manifest, :ca)" " ON CONFLICT (snapshot_id) DO NOTHING" ), {"sid": snapshot_id, "manifest": json.dumps(manifest), "ca": committed_at}, ) muse_snap_count += 1 # Muse commit — derives its ID from snapshot + parent + message. parent_id: str | None = muse_commit_ids[-1] if muse_commit_ids else None # Merge commit every 7 commits (from commit 7 onward) — parent2 is the # commit from 5 positions back, simulating a merged feature branch. # Interval of 7 guarantees ≥5 merges per repo for repos with ≥35 commits. parent2_id: str | None = None if i >= 7 and i % 7 == 0 and len(muse_commit_ids) >= 6: parent2_id = muse_commit_ids[-6] commit_id = _sha(f"muse-c-{snapshot_id}-{parent_id or ''}-{hub_c['message']}") await db.execute( text( "INSERT INTO muse_commits" " (commit_id, repo_id, branch, parent_commit_id, parent2_commit_id," " snapshot_id, message, author, committed_at, created_at, metadata)" " VALUES" " (:cid, :rid, :branch, :pid, :p2id," " :sid, :msg, :author, :cat, :cat, :meta)" " ON CONFLICT (commit_id) DO NOTHING" ), { "cid": commit_id, "rid": repo_id, "branch": hub_c["branch"], "pid": parent_id, "p2id": parent2_id, "sid": snapshot_id, "msg": hub_c["message"], "author": hub_c["author"], "cat": committed_at, "meta": json.dumps(meta), }, ) muse_commit_ids.append(commit_id) muse_commit_count += 1 # Tags: apply cycling taxonomy to every commit. # Rich repos get ALL taxonomy values; others get a representative subset. if is_rich: # Cycle through all 57 tag values across commits so every value appears. tag_val = _ALL_MUSE_TAGS[i % len(_ALL_MUSE_TAGS)] tag_vals = [tag_val] # Also add a second tag from a different category group. second_idx = (i + len(MUSE_EMOTION_TAGS)) % len(_ALL_MUSE_TAGS) if second_idx != i % len(_ALL_MUSE_TAGS): tag_vals.append(_ALL_MUSE_TAGS[second_idx]) else: # Non-rich repos get one tag per commit drawn from a trimmed pool. _trimmed = MUSE_EMOTION_TAGS + MUSE_STAGE_TAGS + MUSE_GENRE_TAGS tag_vals = [_trimmed[i % len(_trimmed)]] for tag_val in tag_vals: tag_id = _uid(f"muse-tag-{commit_id}-{tag_val}") await db.execute( text( "INSERT INTO muse_tags (tag_id, repo_id, commit_id, tag, created_at)" " VALUES (:tid, :rid, :cid, :tag, :ca)" " ON CONFLICT (tag_id) DO NOTHING" ), {"tid": tag_id, "rid": repo_id, "cid": commit_id, "tag": tag_val, "ca": committed_at}, ) muse_tag_count += 1 _prev_objects[repo_id] = prev_objects # Ensure every tag taxonomy value appears at least once in REPO_NEO_SOUL. # Walk through ALL values and seed any that haven't been covered yet. if all_commits.get(REPO_NEO_SOUL): hub_commits_ns = all_commits[REPO_NEO_SOUL] muse_ids_ns: list[str] = [] for i, hub_c in enumerate(hub_commits_ns): snap_seed = f"snap-muse-{REPO_NEO_SOUL}-{i}" snapshot_id = _sha(snap_seed) parent_id_ns: str | None = muse_ids_ns[-1] if muse_ids_ns else None commit_id_ns = _sha(f"muse-c-{snapshot_id}-{parent_id_ns or ''}-{hub_c['message']}") muse_ids_ns.append(commit_id_ns) # Fetch existing tags for REPO_NEO_SOUL. result = await db.execute( text("SELECT tag FROM muse_tags WHERE repo_id = :rid"), {"rid": REPO_NEO_SOUL}, ) existing_tags: set[str] = {row[0] for row in result.fetchall()} missing_tags = [t for t in _ALL_MUSE_TAGS if t not in existing_tags] for j, missing_tag in enumerate(missing_tags): commit_id_ns = muse_ids_ns[j % len(muse_ids_ns)] committed_at_ns = hub_commits_ns[j % len(hub_commits_ns)]["timestamp"] tag_id = _uid(f"muse-tag-fill-{REPO_NEO_SOUL}-{missing_tag}") await db.execute( text( "INSERT INTO muse_tags (tag_id, repo_id, commit_id, tag, created_at)" " VALUES (:tid, :rid, :cid, :tag, :ca)" " ON CONFLICT (tag_id) DO NOTHING" ), {"tid": tag_id, "rid": REPO_NEO_SOUL, "cid": commit_id_ns, "tag": missing_tag, "ca": committed_at_ns}, ) muse_tag_count += 1 await db.flush() print(f" ✅ Muse objects: {muse_obj_count} blobs") print(f" ✅ Muse snapshots: {muse_snap_count} manifests") print(f" ✅ Muse commits: {muse_commit_count} (DAG; includes merge commits)") print(f" ✅ Muse tags: {muse_tag_count} (full taxonomy)") await db.flush() # ── 20. Usage Logs (billing history per user) ───────────────────────────── _USAGE_MODELS = ["anthropic/claude-sonnet-4.6", "anthropic/claude-opus-4.6"] _USAGE_PROMPTS = [ "Add a counter-melody above the baroque theme", "Reharmonize the bridge using tritone substitutions", "Humanize the drum timing across all tracks", "Add string pad layer to the ambient section", "Transpose bass line down a perfect fifth", "Generate a jazz piano voicing for the IV chord", "Thicken the horns arrangement with parallel 3rds", "Reduce note density in bars 9-16 on the keys track", ] usage_count = 0 for uid, uname, _ in USERS: # Only community users generate usage n_logs = 8 + abs(hash(uid)) % 12 # 8-20 usage logs per user for li in range(n_logs): model = _USAGE_MODELS[li % len(_USAGE_MODELS)] prompt = _USAGE_PROMPTS[li % len(_USAGE_PROMPTS)] prompt_tok = 800 + abs(hash(uid + str(li))) % 1200 compl_tok = 300 + abs(hash(uid + str(li) + "c")) % 800 cost = (prompt_tok * 3 + compl_tok * 15) // 100 # ~cents approximation db.add(UsageLog( id=_uid(f"usage-{uid}-{li}"), user_id=uid, prompt=f"[{uname}] {prompt} (iteration {li + 1})", model=model, prompt_tokens=prompt_tok, completion_tokens=compl_tok, cost_cents=cost, created_at=_now(days=60 - li * 3), )) usage_count += 1 print(f" ✅ Usage logs: {usage_count}") await db.flush() # ── 21. Access Tokens (API key tracking) ────────────────────────────────── token_count = 0 for uid, uname, _ in USERS: # 1-3 access tokens per user (active, revoked, expired) n_tokens = 1 + abs(hash(uid)) % 3 for ti in range(n_tokens): is_revoked = ti > 0 and ti % 2 == 0 db.add(AccessToken( id=_uid(f"token-{uid}-{ti}"), user_id=uid, token_hash=_sha(f"tok-{uid}-{ti}-secret"), expires_at=_now(days=-30 * ti) if is_revoked else _now(days=-365), revoked=is_revoked, created_at=_now(days=90 - ti * 15), )) token_count += 1 print(f" ✅ Access tokens: {token_count}") await db.flush() # ── 22. Conversations + Messages + Actions ──────────────────────────────── _CONV_TITLES = [ "Neo-Soul chord voicing session", "Ambient texture generation", "Jazz reharmonization experiment", "Drum groove exploration", "Counter-melody development", "Bridge arrangement help", "Mix balance discussion", "Modal jazz improvisation sketch", ] _MSG_CONTENTS: list[tuple[str, str]] = [ ("user", "Can you add a counter-melody above the baroque theme in bars 9-16?"), ("assistant", "I'll add a counter-melody that follows the harmonic rhythm of the baroque theme. The new voice will move in contrary motion to create interest."), ("user", "Make the counter-melody more chromatic — use passing tones"), ("assistant", "Adding chromatic passing tones between the structural chord tones. The counter-melody now has richer harmonic color while maintaining the baroque style."), ("user", "What key is this section in?"), ("assistant", "The section is in D Dorian — a natural minor mode built on D. The characteristic raised 6th (B natural) gives it the distinctive minor-but-not-sad quality."), ("user", "Add a jazz-influenced harmony to the bridge"), ("assistant", "I'll reharmonize the bridge using tritone substitutions on the dominant chords and quartal voicings on the tonic. This will give it a Coltrane-influenced quality."), ] _ACTION_TYPES = ["generate_notes", "reharmonize", "transpose", "humanize", "analyze"] conv_count = 0 msg_count = 0 action_count = 0 for uid, uname, _ in USERS: n_convs = 3 + abs(hash(uid)) % 4 # 3-7 conversations per user for ci in range(n_convs): conv_id = _uid(f"conv-{uid}-{ci}") title = _CONV_TITLES[(abs(hash(uid)) + ci) % len(_CONV_TITLES)] project_repo = all_repos[(abs(hash(uid)) + ci) % len(all_repos)] db.add(Conversation( id=conv_id, user_id=uid, project_id=project_repo["repo_id"], title=title, is_archived=ci == 0 and abs(hash(uid)) % 3 == 0, project_context={ "repo_id": project_repo["repo_id"], "key_signature": project_repo.get("key_signature"), "tempo_bpm": project_repo.get("tempo_bpm"), }, created_at=_now(days=30 - ci * 5), updated_at=_now(days=30 - ci * 5, hours=ci * 2), )) conv_count += 1 # 4-8 messages per conversation (user/assistant alternating) n_msgs = 4 + (abs(hash(conv_id)) % 5) prev_msg_id: str | None = None for mi in range(min(n_msgs, len(_MSG_CONTENTS))): role, content = _MSG_CONTENTS[mi] msg_id = _uid(f"msg-{conv_id}-{mi}") model_used = _USAGE_MODELS[mi % len(_USAGE_MODELS)] if role == "assistant" else None tok = {"prompt": 800 + mi * 50, "completion": 300 + mi * 30} if role == "assistant" else None db.add(ConversationMessage( id=msg_id, conversation_id=conv_id, role=role, content=content, model_used=model_used, tokens_used=tok, cost_cents=((tok["prompt"] * 3 + tok["completion"] * 15) // 100) if tok else 0, timestamp=_now(days=30 - ci * 5, hours=mi), )) msg_count += 1 # Add an action for assistant messages if role == "assistant" and mi > 0: action_type = _ACTION_TYPES[mi % len(_ACTION_TYPES)] db.add(MessageAction( id=_uid(f"action-{msg_id}"), message_id=msg_id, action_type=action_type, description=f"{action_type.replace('_', ' ').title()} performed on track", success=mi % 5 != 4, # Mostly successful error_message=None if mi % 5 != 4 else "Tool execution timeout", extra_metadata={"track": "keys", "bars": f"{mi * 8}-{mi * 8 + 8}"}, timestamp=_now(days=30 - ci * 5, hours=mi), )) action_count += 1 prev_msg_id = msg_id print(f" ✅ Conversations: {conv_count} Messages: {msg_count} Actions: {action_count}") await db.flush() await db.commit() print() _print_urls(all_commits, session_ids, pr_ids, release_tags) def _print_urls( all_commits: dict[str, list[dict[str, Any]]] | None = None, session_ids: dict[str, list[str]] | None = None, pr_ids: dict[str, list[str]] | None = None, release_tags: dict[str, list[str]] | None = None, ) -> None: """Print all seeded MuseHub URLs to stdout for manual browser verification.""" BASE = "http://localhost:10001/musehub/ui" print() print("=" * 72) print("🎵 MUSEHUB — ALL URLs (localhost:10001)") print("=" * 72) print("\n── User profiles ────────────────────────────────────────────────") for _, uname, _ in list(USERS) + list(COMPOSER_USERS): print(f" {BASE}/users/{uname}") print(f" {BASE}/{uname} (redirects → above)") print("\n── Explore / discover ───────────────────────────────────────────") print(f" {BASE}/explore") print(f" {BASE}/trending") print(f" {BASE}/search") print(f" {BASE}/feed") all_repos_for_urls = list(REPOS[:8]) + list(GENRE_REPOS) for r in all_repos_for_urls: # Skip fork repos from URL dump owner, slug = r["owner"], r["slug"] repo_id = r["repo_id"] rbase = f"{BASE}/{owner}/{slug}" print(f"\n── {owner}/{slug} ─────────────────────────────────────") print(f" Repo: {rbase}") print(f" Graph: {rbase}/graph") print(f" Timeline: {rbase}/timeline") print(f" Insights: {rbase}/insights") print(f" Credits: {rbase}/credits") print(f" Search: {rbase}/search") if all_commits and repo_id in all_commits: commits = all_commits[repo_id] for c in commits[-3:]: print(f" Commit: {rbase}/commits/{c['commit_id'][:12]}") if commits: print(f" Diff: {rbase}/commits/{commits[-1]['commit_id'][:12]}/diff") print(f" Issues: {rbase}/issues") print(f" PRs: {rbase}/pulls") print(f" Releases: {rbase}/releases") if release_tags and repo_id in release_tags: for tag in release_tags[repo_id]: print(f" {rbase}/releases/{tag}") print(f" Sessions: {rbase}/sessions") if session_ids and repo_id in session_ids: for sid in session_ids[repo_id][:2]: print(f" {rbase}/sessions/{sid}") print(f" Divergence:{rbase}/divergence") print(f" Context: {rbase}/context/main") print(f" Analysis: {rbase}/analysis/main/contour") print(f" {rbase}/analysis/main/tempo") print(f" {rbase}/analysis/main/dynamics") print() print("=" * 72) print("✅ Seed complete.") print("=" * 72) async def main() -> None: """CLI entry point. Pass --force to wipe existing seed data before re-seeding.""" force = "--force" in sys.argv db_url: str = settings.database_url or "" engine = create_async_engine(db_url, echo=False) async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) # type: ignore[call-overload] # SQLAlchemy typing: sessionmaker + class_=AsyncSession overload not reflected in stubs async with async_session() as db: await seed(db, force=force) await engine.dispose() if __name__ == "__main__": asyncio.run(main())