livepilot 1.9.22 → 1.9.24

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 (118) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.mcpbignore +40 -0
  3. package/AGENTS.md +3 -3
  4. package/CHANGELOG.md +84 -0
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +141 -72
  7. package/bin/livepilot.js +135 -0
  8. package/livepilot/.Codex-plugin/plugin.json +2 -2
  9. package/livepilot/.claude-plugin/plugin.json +2 -2
  10. package/livepilot/agents/livepilot-producer/AGENT.md +13 -0
  11. package/livepilot/commands/arrange.md +42 -23
  12. package/livepilot/commands/mix.md +34 -19
  13. package/livepilot/commands/perform.md +31 -19
  14. package/livepilot/commands/sounddesign.md +38 -25
  15. package/livepilot/skills/livepilot-arrangement/SKILL.md +2 -1
  16. package/livepilot/skills/livepilot-composition-engine/references/transition-archetypes.md +2 -2
  17. package/livepilot/skills/livepilot-core/SKILL.md +60 -4
  18. package/livepilot/skills/livepilot-core/references/device-atlas/distortion-and-character.md +11 -11
  19. package/livepilot/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +25 -25
  20. package/livepilot/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +21 -21
  21. package/livepilot/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +13 -13
  22. package/livepilot/skills/livepilot-core/references/device-atlas/midi-tools.md +13 -13
  23. package/livepilot/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +5 -5
  24. package/livepilot/skills/livepilot-core/references/device-atlas/space-and-depth.md +16 -16
  25. package/livepilot/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +40 -40
  26. package/livepilot/skills/livepilot-core/references/m4l-devices.md +3 -3
  27. package/livepilot/skills/livepilot-core/references/overview.md +4 -4
  28. package/livepilot/skills/livepilot-evaluation/SKILL.md +12 -8
  29. package/livepilot/skills/livepilot-evaluation/references/memory-promotion.md +2 -2
  30. package/livepilot/skills/livepilot-mix-engine/SKILL.md +1 -1
  31. package/livepilot/skills/livepilot-mix-engine/references/mix-moves.md +2 -2
  32. package/livepilot/skills/livepilot-mixing/SKILL.md +3 -1
  33. package/livepilot/skills/livepilot-notes/SKILL.md +2 -1
  34. package/livepilot/skills/livepilot-release/SKILL.md +29 -15
  35. package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +2 -2
  36. package/livepilot/skills/livepilot-wonder/SKILL.md +62 -0
  37. package/livepilot.mcpb +0 -0
  38. package/manifest.json +91 -0
  39. package/mcp_server/__init__.py +1 -1
  40. package/mcp_server/creative_constraints/__init__.py +6 -0
  41. package/mcp_server/creative_constraints/engine.py +277 -0
  42. package/mcp_server/creative_constraints/models.py +75 -0
  43. package/mcp_server/creative_constraints/tools.py +341 -0
  44. package/mcp_server/experiment/__init__.py +6 -0
  45. package/mcp_server/experiment/engine.py +213 -0
  46. package/mcp_server/experiment/models.py +120 -0
  47. package/mcp_server/experiment/tools.py +263 -0
  48. package/mcp_server/hook_hunter/__init__.py +5 -0
  49. package/mcp_server/hook_hunter/analyzer.py +365 -0
  50. package/mcp_server/hook_hunter/models.py +58 -0
  51. package/mcp_server/hook_hunter/tools.py +588 -0
  52. package/mcp_server/memory/taste_graph.py +328 -0
  53. package/mcp_server/memory/tools.py +99 -0
  54. package/mcp_server/mix_engine/critics.py +2 -2
  55. package/mcp_server/mix_engine/models.py +1 -1
  56. package/mcp_server/mix_engine/state_builder.py +2 -2
  57. package/mcp_server/musical_intelligence/__init__.py +8 -0
  58. package/mcp_server/musical_intelligence/detectors.py +434 -0
  59. package/mcp_server/musical_intelligence/phrase_critic.py +163 -0
  60. package/mcp_server/musical_intelligence/tools.py +224 -0
  61. package/mcp_server/persistence/__init__.py +1 -0
  62. package/mcp_server/persistence/base_store.py +82 -0
  63. package/mcp_server/persistence/project_store.py +106 -0
  64. package/mcp_server/persistence/taste_store.py +122 -0
  65. package/mcp_server/preview_studio/__init__.py +5 -0
  66. package/mcp_server/preview_studio/engine.py +280 -0
  67. package/mcp_server/preview_studio/models.py +74 -0
  68. package/mcp_server/preview_studio/tools.py +466 -0
  69. package/mcp_server/runtime/capability.py +66 -0
  70. package/mcp_server/runtime/capability_probe.py +118 -0
  71. package/mcp_server/runtime/execution_router.py +139 -0
  72. package/mcp_server/runtime/remote_commands.py +82 -0
  73. package/mcp_server/runtime/session_kernel.py +96 -0
  74. package/mcp_server/runtime/tools.py +90 -1
  75. package/mcp_server/semantic_moves/__init__.py +13 -0
  76. package/mcp_server/semantic_moves/compiler.py +116 -0
  77. package/mcp_server/semantic_moves/mix_compilers.py +291 -0
  78. package/mcp_server/semantic_moves/mix_moves.py +157 -0
  79. package/mcp_server/semantic_moves/models.py +46 -0
  80. package/mcp_server/semantic_moves/performance_compilers.py +208 -0
  81. package/mcp_server/semantic_moves/performance_moves.py +81 -0
  82. package/mcp_server/semantic_moves/registry.py +32 -0
  83. package/mcp_server/semantic_moves/resolvers.py +126 -0
  84. package/mcp_server/semantic_moves/sound_design_compilers.py +266 -0
  85. package/mcp_server/semantic_moves/sound_design_moves.py +78 -0
  86. package/mcp_server/semantic_moves/tools.py +205 -0
  87. package/mcp_server/semantic_moves/transition_compilers.py +222 -0
  88. package/mcp_server/semantic_moves/transition_moves.py +76 -0
  89. package/mcp_server/server.py +10 -0
  90. package/mcp_server/services/__init__.py +1 -0
  91. package/mcp_server/services/motif_service.py +67 -0
  92. package/mcp_server/session_continuity/__init__.py +6 -0
  93. package/mcp_server/session_continuity/models.py +86 -0
  94. package/mcp_server/session_continuity/tools.py +230 -0
  95. package/mcp_server/session_continuity/tracker.py +263 -0
  96. package/mcp_server/song_brain/__init__.py +6 -0
  97. package/mcp_server/song_brain/builder.py +504 -0
  98. package/mcp_server/song_brain/models.py +136 -0
  99. package/mcp_server/song_brain/tools.py +312 -0
  100. package/mcp_server/stuckness_detector/__init__.py +5 -0
  101. package/mcp_server/stuckness_detector/detector.py +400 -0
  102. package/mcp_server/stuckness_detector/models.py +66 -0
  103. package/mcp_server/stuckness_detector/tools.py +195 -0
  104. package/mcp_server/tools/_conductor.py +104 -6
  105. package/mcp_server/tools/analyzer.py +1 -1
  106. package/mcp_server/tools/devices.py +34 -0
  107. package/mcp_server/wonder_mode/__init__.py +6 -0
  108. package/mcp_server/wonder_mode/diagnosis.py +84 -0
  109. package/mcp_server/wonder_mode/engine.py +493 -0
  110. package/mcp_server/wonder_mode/session.py +114 -0
  111. package/mcp_server/wonder_mode/tools.py +290 -0
  112. package/package.json +2 -2
  113. package/remote_script/LivePilot/__init__.py +1 -1
  114. package/remote_script/LivePilot/browser.py +4 -1
  115. package/remote_script/LivePilot/devices.py +29 -0
  116. package/remote_script/LivePilot/tracks.py +11 -4
  117. package/scripts/generate_tool_catalog.py +131 -0
  118. package/scripts/sync_metadata.py +132 -0
@@ -0,0 +1,312 @@
1
+ """SongBrain MCP tools — 3 tools for song identity modeling.
2
+
3
+ build_song_brain — construct the musical identity of the current piece
4
+ explain_song_identity — human-readable summary of what the song is about
5
+ detect_identity_drift — compare before/after to detect identity damage
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from fastmcp import Context
11
+
12
+ from ..server import mcp
13
+ from . import builder
14
+ from .models import SongBrain
15
+
16
+
17
+ # Module-level fallback for consumers without ctx.
18
+ # Prefer ctx.lifespan_context["current_brain"] when ctx is available.
19
+ _current_brain: SongBrain | None = None
20
+
21
+ # Snapshot store: brain_id -> SongBrain, max 10 snapshots
22
+ _brain_snapshots: dict[str, SongBrain] = {}
23
+ _MAX_SNAPSHOTS = 10
24
+
25
+
26
+ def _set_brain(ctx: Context, brain: SongBrain) -> None:
27
+ """Store brain in lifespan_context, module fallback, and snapshot store."""
28
+ global _current_brain
29
+ _current_brain = brain
30
+ ctx.lifespan_context["current_brain"] = brain
31
+ # Save snapshot for later drift comparison
32
+ _brain_snapshots[brain.brain_id] = brain
33
+ # Evict oldest if over limit
34
+ while len(_brain_snapshots) > _MAX_SNAPSHOTS:
35
+ oldest_key = next(iter(_brain_snapshots))
36
+ del _brain_snapshots[oldest_key]
37
+
38
+
39
+ def _get_snapshot(brain_id: str) -> SongBrain | None:
40
+ """Retrieve a past brain snapshot by ID."""
41
+ return _brain_snapshots.get(brain_id)
42
+
43
+
44
+ def _get_ableton(ctx: Context):
45
+ return ctx.lifespan_context["ableton"]
46
+
47
+
48
+ def _fetch_session_data(ctx: Context) -> dict:
49
+ """Fetch all available session data for brain building.
50
+
51
+ Populates real data from Ableton and pure-computation modules:
52
+ - motif_data: from get_motif_graph (motif engine)
53
+ - composition_analysis: from musical intelligence section inference
54
+ - role_graph: from semantic move resolvers (track role inference)
55
+ - recent_moves: from session-scoped action ledger
56
+ """
57
+ ableton = _get_ableton(ctx)
58
+ data: dict = {
59
+ "session_info": {},
60
+ "scenes": [],
61
+ "tracks": [],
62
+ "motif_data": {},
63
+ "composition_analysis": {},
64
+ "role_graph": {},
65
+ "recent_moves": [],
66
+ }
67
+
68
+ try:
69
+ data["session_info"] = ableton.send_command("get_session_info", {})
70
+ except Exception:
71
+ data["session_info"] = {"tempo": 120.0, "track_count": 0}
72
+
73
+ try:
74
+ matrix = ableton.send_command("get_scene_matrix")
75
+ data["scenes"] = [
76
+ {"name": s.get("name", f"Scene {i}"), "clips": row}
77
+ for i, (s, row) in enumerate(
78
+ zip(matrix.get("scenes", []), matrix.get("matrix", []))
79
+ )
80
+ ]
81
+ except Exception:
82
+ pass
83
+
84
+ try:
85
+ info = data["session_info"]
86
+ tracks_list = info.get("tracks", [])
87
+ data["tracks"] = tracks_list if isinstance(tracks_list, list) else []
88
+ except Exception:
89
+ pass
90
+
91
+ # Motif data — via shared motif service (pure-Python, not TCP)
92
+ try:
93
+ from ..services.motif_service import get_motif_data, fetch_notes_from_ableton
94
+ notes_by_track = fetch_notes_from_ableton(ableton, data.get("tracks", []))
95
+ data["motif_data"] = get_motif_data(notes_by_track)
96
+ except Exception:
97
+ pass # Motif graph requires notes in clips; empty is valid
98
+
99
+ # Composition analysis — from musical intelligence detectors (pure computation)
100
+ try:
101
+ from ..musical_intelligence import detectors
102
+ total_tracks = data["session_info"].get("track_count", 6)
103
+ purposes = detectors.infer_section_purposes(data["scenes"], total_tracks)
104
+ arc = detectors.score_emotional_arc(purposes)
105
+ data["composition_analysis"] = {
106
+ "sections": [p.to_dict() for p in purposes],
107
+ "emotional_arc": arc.to_dict(),
108
+ }
109
+ except Exception:
110
+ pass
111
+
112
+ # Role graph — from semantic move resolvers (pure computation, no I/O)
113
+ try:
114
+ from ..semantic_moves.resolvers import infer_role
115
+ roles = {}
116
+ for track in data["tracks"]:
117
+ name = track.get("name", "")
118
+ role = infer_role(name)
119
+ roles[name] = {"index": track.get("index", 0), "role": role}
120
+ data["role_graph"] = roles
121
+ except Exception:
122
+ pass
123
+
124
+ # Recent moves — from session-scoped action ledger
125
+ try:
126
+ from ..runtime.action_ledger import SessionLedger
127
+ ledger = ctx.lifespan_context.get("action_ledger")
128
+ if isinstance(ledger, SessionLedger):
129
+ recent = ledger.get_recent_moves(limit=10)
130
+ data["recent_moves"] = [e.to_dict() for e in recent]
131
+ except Exception:
132
+ pass
133
+
134
+ return data
135
+
136
+
137
+ @mcp.tool()
138
+ def build_song_brain(ctx: Context) -> dict:
139
+ """Build the musical identity model for the current song.
140
+
141
+ Analyzes the session to identify:
142
+ - identity_core: the strongest defining idea
143
+ - sacred_elements: motifs/textures/grooves that must be preserved
144
+ - section_purposes: what each section is trying to do emotionally
145
+ - energy_arc: rise/fall shape across sections
146
+ - open_questions: what the song has not resolved yet
147
+
148
+ Call this at the start of complex creative workflows.
149
+ Returns the full SongBrain as a dict.
150
+ """
151
+ data = _fetch_session_data(ctx)
152
+
153
+ # Capability reporting — what data was actually available
154
+ from ..runtime.capability import build_capability
155
+ cap = build_capability(
156
+ required=["session_info", "scenes", "tracks", "motif_data", "composition_analysis", "role_graph"],
157
+ available={
158
+ "session_info": bool(data.get("session_info", {}).get("tempo")),
159
+ "scenes": bool(data.get("scenes")),
160
+ "tracks": bool(data.get("tracks")),
161
+ "motif_data": bool(data.get("motif_data")),
162
+ "composition_analysis": bool(data.get("composition_analysis")),
163
+ "role_graph": bool(data.get("role_graph")),
164
+ },
165
+ )
166
+
167
+ brain = builder.build_song_brain(
168
+ session_info=data["session_info"],
169
+ scenes=data["scenes"],
170
+ tracks=data["tracks"],
171
+ motif_data=data["motif_data"],
172
+ composition_analysis=data["composition_analysis"],
173
+ role_graph=data["role_graph"],
174
+ recent_moves=data["recent_moves"],
175
+ )
176
+ _set_brain(ctx, brain)
177
+
178
+ return {
179
+ **brain.to_dict(),
180
+ "summary": brain.summary,
181
+ "capability": cap.to_dict(),
182
+ }
183
+
184
+
185
+ @mcp.tool()
186
+ def explain_song_identity(ctx: Context) -> dict:
187
+ """Explain the current song's identity in human musical language.
188
+
189
+ If no SongBrain exists yet, builds one first. Returns a structured
190
+ explanation suitable for the agent to talk about the song naturally.
191
+ """
192
+ if _current_brain is None:
193
+ data = _fetch_session_data(ctx)
194
+ brain = builder.build_song_brain(
195
+ session_info=data["session_info"],
196
+ scenes=data["scenes"],
197
+ tracks=data["tracks"],
198
+ motif_data=data["motif_data"],
199
+ composition_analysis=data["composition_analysis"],
200
+ role_graph=data["role_graph"],
201
+ recent_moves=data["recent_moves"],
202
+ )
203
+ _set_brain(ctx, brain)
204
+
205
+ brain = _current_brain
206
+ explanation: dict = {
207
+ "identity": brain.identity_core,
208
+ "confidence": brain.identity_confidence,
209
+ }
210
+
211
+ # Sacred elements in natural language
212
+ if brain.sacred_elements:
213
+ explanation["protect"] = [
214
+ f"{e.element_type}: {e.description}" for e in brain.sacred_elements
215
+ ]
216
+ else:
217
+ explanation["protect"] = ["No clearly sacred elements detected yet"]
218
+
219
+ # What each section does
220
+ if brain.section_purposes:
221
+ explanation["sections"] = [
222
+ f"{s.label} — {s.emotional_intent} (energy {s.energy_level:.0%})"
223
+ for s in brain.section_purposes
224
+ ]
225
+
226
+ # Energy shape
227
+ if brain.energy_arc:
228
+ arc = brain.energy_arc
229
+ if len(arc) >= 3:
230
+ peak_idx = arc.index(max(arc))
231
+ peak_pct = peak_idx / max(len(arc) - 1, 1)
232
+ if peak_pct < 0.3:
233
+ explanation["energy_shape"] = "front-loaded — peaks early"
234
+ elif peak_pct > 0.7:
235
+ explanation["energy_shape"] = "slow burn — builds to late peak"
236
+ else:
237
+ explanation["energy_shape"] = "centered arc — peaks in the middle"
238
+ else:
239
+ explanation["energy_shape"] = "short form — limited arc data"
240
+
241
+ # Open questions
242
+ if brain.open_questions:
243
+ explanation["open_questions"] = [q.question for q in brain.open_questions]
244
+
245
+ # Drift warning
246
+ if brain.identity_drift_risk > 0.3:
247
+ explanation["warning"] = (
248
+ f"Identity drift risk is {brain.identity_drift_risk:.0%} — "
249
+ "recent edits may be moving the song away from itself"
250
+ )
251
+
252
+ explanation["summary"] = brain.summary
253
+ return explanation
254
+
255
+
256
+ @mcp.tool()
257
+ def detect_identity_drift(
258
+ ctx: Context,
259
+ before_brain_id: str = "",
260
+ ) -> dict:
261
+ """Detect whether recent changes have damaged the song's identity.
262
+
263
+ Compares the current state against a previous SongBrain snapshot.
264
+ If before_brain_id is provided, looks up that specific snapshot.
265
+ If empty, uses the last cached brain.
266
+ If no previous brain exists, builds baseline and reports no drift.
267
+
268
+ before_brain_id: optional brain_id from a previous build_song_brain call.
269
+
270
+ Returns drift score, changed elements, sacred damage, and recommendation.
271
+ """
272
+ # Look up the "before" brain — by ID if provided, else use last cached
273
+ if before_brain_id:
274
+ before = _get_snapshot(before_brain_id)
275
+ if before is None:
276
+ available = list(_brain_snapshots.keys())
277
+ return {
278
+ "error": f"No snapshot found for brain_id '{before_brain_id}'",
279
+ "available_snapshots": available,
280
+ }
281
+ else:
282
+ before = _current_brain
283
+
284
+ # Build fresh brain from current state
285
+ data = _fetch_session_data(ctx)
286
+ after = builder.build_song_brain(
287
+ session_info=data["session_info"],
288
+ scenes=data["scenes"],
289
+ tracks=data["tracks"],
290
+ motif_data=data["motif_data"],
291
+ composition_analysis=data["composition_analysis"],
292
+ role_graph=data["role_graph"],
293
+ recent_moves=data["recent_moves"],
294
+ )
295
+
296
+ if before is None:
297
+ _set_brain(ctx, after)
298
+ return {
299
+ "drift_score": 0.0,
300
+ "note": "No previous brain to compare — this is the baseline",
301
+ "brain_id": after.brain_id,
302
+ "recommendation": "safe",
303
+ }
304
+
305
+ drift = builder.detect_identity_drift(before, after)
306
+ _set_brain(ctx, after)
307
+
308
+ return {
309
+ **drift.to_dict(),
310
+ "before_brain_id": before.brain_id,
311
+ "after_brain_id": after.brain_id,
312
+ }
@@ -0,0 +1,5 @@
1
+ """Stuckness Detector — momentum rescue for Stage 2.
2
+
3
+ Identifies when the session is losing momentum and shifts the agent
4
+ from micro-fixing into directional help.
5
+ """