livepilot 1.23.6 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +60 -14
  3. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  4. package/m4l_device/livepilot_bridge.js +1 -1
  5. package/mcp_server/__init__.py +1 -1
  6. package/mcp_server/atlas/__init__.py +17 -3
  7. package/mcp_server/atlas/explore_tools.py +332 -0
  8. package/mcp_server/atlas/tools.py +161 -0
  9. package/mcp_server/audit/__init__.py +6 -0
  10. package/mcp_server/audit/checks.py +618 -0
  11. package/mcp_server/audit/tools.py +232 -0
  12. package/mcp_server/composer/branch_producer.py +5 -2
  13. package/mcp_server/composer/develop/__init__.py +19 -0
  14. package/mcp_server/composer/develop/apply.py +217 -0
  15. package/mcp_server/composer/develop/brief_builder.py +269 -0
  16. package/mcp_server/composer/develop/seed_introspector.py +195 -0
  17. package/mcp_server/composer/engine.py +15 -521
  18. package/mcp_server/composer/fast/__init__.py +62 -0
  19. package/mcp_server/composer/fast/apply.py +533 -0
  20. package/mcp_server/composer/fast/brief_builder.py +1479 -0
  21. package/mcp_server/composer/fast/tier_classification.py +159 -0
  22. package/mcp_server/composer/framework/__init__.py +0 -0
  23. package/mcp_server/composer/framework/applier.py +179 -0
  24. package/mcp_server/composer/framework/artist_loader.py +63 -0
  25. package/mcp_server/composer/framework/atlas_resolver.py +554 -0
  26. package/mcp_server/composer/framework/brief.py +79 -0
  27. package/mcp_server/composer/framework/event_lexicon.py +71 -0
  28. package/mcp_server/composer/framework/genre_loader.py +77 -0
  29. package/mcp_server/composer/framework/intent_source.py +137 -0
  30. package/mcp_server/composer/framework/knowledge_pack.py +140 -0
  31. package/mcp_server/composer/framework/plan_compiler.py +10 -0
  32. package/mcp_server/composer/full/__init__.py +10 -0
  33. package/mcp_server/composer/full/apply.py +1139 -0
  34. package/mcp_server/composer/full/brief_builder.py +227 -0
  35. package/mcp_server/composer/full/engine.py +541 -0
  36. package/mcp_server/composer/full/layer_planner.py +491 -0
  37. package/mcp_server/composer/layer_planner.py +19 -465
  38. package/mcp_server/composer/sample_resolver.py +80 -7
  39. package/mcp_server/composer/tools.py +626 -28
  40. package/mcp_server/server.py +1 -0
  41. package/mcp_server/splice_client/client.py +7 -0
  42. package/mcp_server/tools/_analyzer_engine/sample.py +172 -7
  43. package/mcp_server/tools/_planner_engine.py +25 -63
  44. package/mcp_server/tools/analyzer.py +10 -4
  45. package/mcp_server/tools/browser.py +102 -19
  46. package/package.json +2 -2
  47. package/remote_script/LivePilot/__init__.py +1 -1
  48. package/server.json +3 -3
@@ -0,0 +1,227 @@
1
+ """Full-mode brief builder — Phase 1 of the LLM-creative two-phase flow.
2
+
3
+ Takes a prompt (and optional seed_state for "extend an existing project"
4
+ flows) and returns a brief carrying VOCABULARY for the agent to design
5
+ the song's form from.
6
+
7
+ CRITICAL: The brief MUST NOT contain predetermined section sequences, bar
8
+ counts, or fixed variant taxonomies. The agent decides the form per call.
9
+ The framework only provides:
10
+ - Parsed intent (genre/mood/tempo/key from the prompt)
11
+ - Genre/artist character vocabulary (descriptive)
12
+ - The 42-event structural lexicon (named primitives, not a sequence)
13
+ - Atlas instrument candidates per role
14
+ - Live manual snippets for likely devices
15
+ - Optional seed_state for extension flows
16
+ - Research hooks (WebSearch directives for niche styles)
17
+ - An open-ended design_targets text describing the variation surface
18
+
19
+ Phase 1 stubs: genre_context, atlas_candidates_per_role, manual_snippets,
20
+ event_lexicon — empty values now, populated by Phase 4 KnowledgePack.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import asdict, is_dataclass
26
+ from typing import Any, Optional
27
+
28
+ from ..prompt_parser import parse_prompt
29
+ from ..develop.brief_builder import (
30
+ extract_artist_refs,
31
+ detect_research_hooks,
32
+ )
33
+ from ..framework.knowledge_pack import KnowledgePack
34
+
35
+
36
+ # ── design targets ─────────────────────────────────────────────────
37
+
38
+ _DESIGN_TARGETS = (
39
+ "Design a full-track arrangement from the prompt and (when provided) "
40
+ "the seed_state. You decide every aspect of the form: section sequence, "
41
+ "section bar counts, drop placement (or absence), breakdown placement, "
42
+ "outro length, hook reveal/withholding/restatement schedule, element "
43
+ "entry/exit choreography across the timeline. Use the genre_context and "
44
+ "artist_context as flavor (sonic character, gear preferences, harmonic "
45
+ "stance) — they describe what a style sounds like, NOT how to structure "
46
+ "the song. Use the event_lexicon as a vocabulary of named structural "
47
+ "moves to schedule at chosen phrase boundaries. For niche style references "
48
+ "in research_hooks, run WebSearch to ground your form choices in the "
49
+ "actual conventions of that subgenre.\n\n"
50
+ "INSTRUMENT SELECTION (v1.25 hybrid knowledge surface — MANDATED FOUR-SOURCE SEARCH):\n"
51
+ "The brief's `atlas_anchors` is ONE source. Before committing any role pick "
52
+ "you MUST also query the other three sources below. Factory-atlas-only picks "
53
+ "have repeatedly missed canonical user-curated instruments (e.g., the 808 Trap "
54
+ "Selector Rack from Trap Drums by Sound Oracle pack lives in the packs overlay, "
55
+ "not the factory tag index). Always union BEFORE deciding.\n\n"
56
+ "Source 1 — Factory atlas (already surfaced in atlas_anchors). Three tools:\n"
57
+ " • atlas_audition(uri) — full sidecar dump for a chosen URI: character "
58
+ "tags, signature_techniques, producer-curated macro values, related demos. "
59
+ "Call this BEFORE committing to a candidate when its tags alone aren't "
60
+ "enough to know if it fits the section.\n"
61
+ " • atlas_explore(role, mood, genre, artists?) — refined per-role query "
62
+ "when an anchor doesn't fit the section's purpose, or when you need siblings "
63
+ "of a role pick. Returns 3-5 ranked candidates with reasoning trails.\n"
64
+ " • atlas_substitute(current_uri, anti_tag) — anti-tag-driven swap to use "
65
+ "AFTER analyze_sound_design or analyze_mix flags an issue (\"too bright\", "
66
+ "\"too aggressive\", \"too sparse\", \"muddy\", \"static\", \"generic\"). "
67
+ "Returns 3 alternatives that explicitly avoid the unwanted property.\n\n"
68
+ "Source 2 — User corpus (mandatory union — ~/.livepilot/atlas-overlays/):\n"
69
+ " • extension_atlas_search(query=\"<role>\", limit=10) — searches all four "
70
+ "overlay namespaces: `packs` (Ableton factory packs with hidden_gems, "
71
+ "notable_presets, signature_workflows fields), `m4l-devices` (curated M4L "
72
+ "device knowledge), `user.*` (your scanned .amxd / plugin / preset library), "
73
+ "`elektron` (hardware-mirror chains). Producer-curated rack instruments "
74
+ "(808 Trap Selector Rack, Harmonic Drone Generator, etc.) live here, NOT "
75
+ "in the factory tag index. Always run this query alongside atlas_explore.\n"
76
+ " • extension_atlas_search(query=\"<role-or-aesthetic>\", entity_type=\"demo_project\") — "
77
+ "GROUND-TRUTH ROLE→URI MAPPING. The packs namespace contains 100+ analyzed "
78
+ "demo .als project parses; each carries actual track-by-track instrument "
79
+ "URIs proven on real Ableton-shipped demos. For 808 bass, query "
80
+ "`demo_project` to find which pack-included .als demos use 808 bass and "
81
+ "what URI they loaded — the highest-confidence source for any role.\n"
82
+ " • extension_atlas_get(namespace, entity_id) — full body of a chosen entry "
83
+ "including hidden_gems and signature_workflows fields the search summary trims.\n\n"
84
+ "Source 3 — Anthropic Ableton Knowledge MCP (mcp__Ableton_Knowledge__*):\n"
85
+ " • search_transcripts(query) — Ableton's official tutorial video transcripts; "
86
+ "ground-truth pedagogy on how producers actually use the device.\n"
87
+ " • search_live_manual(query) — live manual snippets for any device or feature.\n"
88
+ " • search_knowledge_base(query) — broader Ableton knowledge base.\n"
89
+ " • search_videos(query) — official tutorial video metadata.\n"
90
+ " Run these for the ROLE term (e.g., \"808 bass\", \"sidechain compression\") "
91
+ "and for any artist/genre reference in the prompt to ground your design choices.\n\n"
92
+ "FOUR-SOURCE SEARCH PROTOCOL per role:\n"
93
+ " 1. Read atlas_anchors[role] (Source 1 starting point).\n"
94
+ " 2. Call atlas_explore(role, mood, genre, artists) (Source 1 ranked alts).\n"
95
+ " 3. Call extension_atlas_search(query=role) and extension_atlas_search(query=role, "
96
+ "entity_type=\"demo_project\") (Source 2 user corpus + ground-truth demos).\n"
97
+ " 4. Call mcp__Ableton_Knowledge__search_transcripts(query=role) for context (Source 3).\n"
98
+ " 5. Union results, score against brief's mood/aesthetic, then commit.\n\n"
99
+ "The framework's job was to surface the corpus. The picks are yours. "
100
+ "Submit your design as a plan to compose_full_apply with: per-track variant "
101
+ "clips at chosen scene slots, per-section arrangement_clip placements "
102
+ "referencing those variants, and structural events scheduled at phrase "
103
+ "boundaries. The form is YOUR creative product — vocabularies tell you "
104
+ "what techno or BoC sound like, they do not tell you the bar count of an intro."
105
+ )
106
+
107
+
108
+ # ── tempo extraction ───────────────────────────────────────────────
109
+
110
+ def _extract_tempo_from_intent(intent: Any) -> Optional[float]:
111
+ """Pull tempo from CompositionIntent (dataclass or dict)."""
112
+ if is_dataclass(intent):
113
+ intent = asdict(intent)
114
+ if not isinstance(intent, dict):
115
+ return None
116
+ val = intent.get("tempo")
117
+ if val is None:
118
+ return None
119
+ try:
120
+ return float(val)
121
+ except (TypeError, ValueError):
122
+ return None
123
+
124
+
125
+ def _extract_key_from_intent(intent: Any) -> Optional[str]:
126
+ """Pull key from CompositionIntent (dataclass or dict)."""
127
+ if is_dataclass(intent):
128
+ intent = asdict(intent)
129
+ if not isinstance(intent, dict):
130
+ return None
131
+ return intent.get("key")
132
+
133
+
134
+ def _intent_to_dict(intent: Any) -> dict:
135
+ if is_dataclass(intent):
136
+ return asdict(intent)
137
+ if isinstance(intent, dict):
138
+ return intent
139
+ return {"raw_intent": str(intent)}
140
+
141
+
142
+ # ── main entry point ───────────────────────────────────────────────
143
+
144
+ def build_full_brief(
145
+ ctx: Any,
146
+ prompt: str,
147
+ seed_state: Optional[dict] = None,
148
+ ) -> dict:
149
+ """Build a Phase-1 full-mode brief.
150
+
151
+ Args:
152
+ ctx: Lifespan context — Phase 4 KnowledgePack will use this to fetch
153
+ atlas candidates + manual snippets. Phase 1 stub doesn't need it.
154
+ prompt: free-text directive ("dark techno 128bpm in Am",
155
+ "make it sound like Burial", etc.)
156
+ seed_state: optional dict from develop's introspect_seed — when
157
+ present, full mode extends the existing project; when
158
+ None, full mode generates from prompt only.
159
+
160
+ Returns dict with vocabulary fields. NEVER returns form-prescriptive fields.
161
+ """
162
+ parsed_intent = parse_prompt(prompt)
163
+ intent_dict = _intent_to_dict(parsed_intent)
164
+
165
+ # Tempo + key precedence: seed wins when present, else prompt
166
+ seed_tempo = seed_state.get("tempo") if seed_state else None
167
+ seed_key = seed_state.get("key") if seed_state else None
168
+ tempo = seed_tempo if seed_tempo is not None else _extract_tempo_from_intent(parsed_intent)
169
+ key = seed_key if seed_key is not None else _extract_key_from_intent(parsed_intent)
170
+
171
+ # Vocabulary lookups
172
+ artist_refs = extract_artist_refs(prompt or "")
173
+ research_hooks = detect_research_hooks(prompt or "")
174
+
175
+ # Build knowledge pack — populates genre_context, artist_context, event_lexicon,
176
+ # AND v1.25 atlas_anchors when atlas + brief_text are available.
177
+ if artist_refs:
178
+ intent_dict["artists"] = artist_refs
179
+ kp = KnowledgePack()
180
+ atlas_obj = _safe_get_atlas(ctx)
181
+ ableton_obj = _safe_get_ableton(ctx)
182
+ knowledge = kp.build(
183
+ intent_dict,
184
+ mode="full",
185
+ atlas=atlas_obj,
186
+ ableton=ableton_obj,
187
+ ctx=ctx,
188
+ brief_text=prompt or "",
189
+ )
190
+
191
+ return {
192
+ "mode": "full",
193
+ "tempo": tempo,
194
+ "key": key,
195
+ "parsed_intent": intent_dict,
196
+ "genre_context": knowledge["genre_context"],
197
+ "artist_context": knowledge["artist_context"],
198
+ "event_lexicon": knowledge["event_lexicon"],
199
+ "atlas_candidates_per_role": knowledge["atlas_candidates_per_role"],
200
+ "atlas_anchors": knowledge.get("atlas_anchors"),
201
+ "manual_snippets": knowledge["manual_snippets"],
202
+ "seed_state": seed_state,
203
+ "research_hooks": research_hooks,
204
+ "design_targets": _DESIGN_TARGETS,
205
+ }
206
+
207
+
208
+ # ── Lifespan-context helpers ───────────────────────────────────────
209
+
210
+
211
+ def _safe_get_atlas(ctx: Any) -> Optional[Any]:
212
+ """Best-effort atlas fetch. Returns None on any failure."""
213
+ try:
214
+ from ...atlas import get_atlas
215
+ return get_atlas()
216
+ except Exception:
217
+ return None
218
+
219
+
220
+ def _safe_get_ableton(ctx: Any) -> Optional[Any]:
221
+ """Best-effort ableton-client fetch from lifespan_context. None on miss."""
222
+ try:
223
+ if ctx is not None and hasattr(ctx, "lifespan_context"):
224
+ return ctx.lifespan_context.get("ableton")
225
+ except Exception:
226
+ pass
227
+ return None