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,280 @@
1
+ """Preview Studio engine — pure computation, zero I/O.
2
+
3
+ Creates, compares, and ranks preview variants using the creative triptych
4
+ pattern (safe / strong / unexpected).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import json
11
+ import time
12
+ from typing import Optional
13
+
14
+ from .models import PreviewSet, PreviewVariant
15
+
16
+
17
+ # ── In-memory store ───────────────────────────────────────────────
18
+
19
+ _preview_sets: dict[str, PreviewSet] = {}
20
+ _MAX_PREVIEW_SETS = 20
21
+
22
+
23
+ def get_preview_set(set_id: str) -> Optional[PreviewSet]:
24
+ return _preview_sets.get(set_id)
25
+
26
+
27
+ def store_preview_set(ps: PreviewSet) -> None:
28
+ _preview_sets[ps.set_id] = ps
29
+ # Evict oldest sets if over limit
30
+ while len(_preview_sets) > _MAX_PREVIEW_SETS:
31
+ oldest_key = next(iter(_preview_sets))
32
+ del _preview_sets[oldest_key]
33
+
34
+
35
+ # ── Creation ──────────────────────────────────────────────────────
36
+
37
+
38
+ def create_preview_set(
39
+ request_text: str,
40
+ kernel_id: str,
41
+ strategy: str = "creative_triptych",
42
+ available_moves: Optional[list[dict]] = None,
43
+ song_brain: Optional[dict] = None,
44
+ taste_graph: Optional[dict] = None,
45
+ ) -> PreviewSet:
46
+ """Create a preview set with variant slots.
47
+
48
+ For creative_triptych, generates 3 variants: safe, strong, unexpected.
49
+ Each variant gets a move_id from available_moves ranked by novelty.
50
+ """
51
+ set_id = _compute_set_id(request_text, kernel_id)
52
+ now = int(time.time() * 1000)
53
+
54
+ moves = available_moves or []
55
+ song_brain = song_brain or {}
56
+ taste_graph = taste_graph or {}
57
+
58
+ if strategy == "creative_triptych":
59
+ variants = _build_triptych(request_text, moves, song_brain, taste_graph, set_id, now)
60
+ elif strategy == "binary":
61
+ variants = _build_binary(request_text, moves, song_brain, set_id, now)
62
+ else:
63
+ variants = _build_triptych(request_text, moves, song_brain, taste_graph, set_id, now)
64
+
65
+ ps = PreviewSet(
66
+ set_id=set_id,
67
+ request_text=request_text,
68
+ strategy=strategy,
69
+ source_kernel_id=kernel_id,
70
+ variants=variants,
71
+ created_at_ms=now,
72
+ )
73
+ store_preview_set(ps)
74
+ return ps
75
+
76
+
77
+ def _build_triptych(
78
+ request_text: str,
79
+ moves: list[dict],
80
+ song_brain: dict,
81
+ taste_graph: dict,
82
+ set_id: str,
83
+ now: int,
84
+ ) -> list[PreviewVariant]:
85
+ """Build safe / strong / unexpected variants."""
86
+ identity = song_brain.get("identity_core", "")
87
+ sacred = [e.get("description", "") for e in song_brain.get("sacred_elements", [])]
88
+ sacred_text = ", ".join(sacred[:3]) if sacred else "core elements"
89
+
90
+ profiles = [
91
+ {
92
+ "label": "safe",
93
+ "novelty": 0.2,
94
+ "intent": f"Close to current identity, minimal risk. {request_text}",
95
+ "identity_effect": "preserves",
96
+ "what_preserved": f"Preserves {sacred_text}",
97
+ "why_it_matters": "Low risk — good when identity is fragile",
98
+ },
99
+ {
100
+ "label": "strong",
101
+ "novelty": 0.5,
102
+ "intent": f"Musically assertive approach. {request_text}",
103
+ "identity_effect": "evolves",
104
+ "what_preserved": f"Maintains {sacred_text} while pushing forward",
105
+ "why_it_matters": "Best balance of impact and safety",
106
+ },
107
+ {
108
+ "label": "unexpected",
109
+ "novelty": 0.8,
110
+ "intent": f"Surprising but taste-filtered. {request_text}",
111
+ "identity_effect": "contrasts",
112
+ "what_preserved": f"Respects {sacred_text} but reframes context",
113
+ "why_it_matters": "High novelty — may unlock a new direction",
114
+ },
115
+ ]
116
+
117
+ variants = []
118
+ for i, profile in enumerate(profiles):
119
+ # Pick a move if available
120
+ move_id = ""
121
+ compiled_plan = None
122
+ if moves and i < len(moves):
123
+ move_id = moves[i].get("move_id", "")
124
+ compiled_plan = moves[i].get("compile_plan")
125
+
126
+ variants.append(PreviewVariant(
127
+ variant_id=f"{set_id}_{profile['label']}",
128
+ label=profile["label"],
129
+ intent=profile["intent"],
130
+ novelty_level=profile["novelty"],
131
+ identity_effect=profile["identity_effect"],
132
+ what_preserved=profile["what_preserved"],
133
+ why_it_matters=profile["why_it_matters"],
134
+ move_id=move_id,
135
+ compiled_plan=compiled_plan,
136
+ taste_fit=_estimate_taste_fit(profile["novelty"], taste_graph),
137
+ created_at_ms=now,
138
+ ))
139
+
140
+ return variants
141
+
142
+
143
+ def _build_binary(
144
+ request_text: str,
145
+ moves: list[dict],
146
+ song_brain: dict,
147
+ set_id: str,
148
+ now: int,
149
+ ) -> list[PreviewVariant]:
150
+ """Build simple A/B comparison."""
151
+ return [
152
+ PreviewVariant(
153
+ variant_id=f"{set_id}_a",
154
+ label="option_a",
155
+ intent=f"Primary approach: {request_text}",
156
+ novelty_level=0.3,
157
+ identity_effect="preserves",
158
+ move_id=moves[0].get("move_id", "") if moves else "",
159
+ created_at_ms=now,
160
+ ),
161
+ PreviewVariant(
162
+ variant_id=f"{set_id}_b",
163
+ label="option_b",
164
+ intent=f"Alternative approach: {request_text}",
165
+ novelty_level=0.6,
166
+ identity_effect="evolves",
167
+ move_id=moves[1].get("move_id", "") if len(moves) > 1 else "",
168
+ created_at_ms=now,
169
+ ),
170
+ ]
171
+
172
+
173
+ # ── Comparison ────────────────────────────────────────────────────
174
+
175
+
176
+ def compare_variants(
177
+ preview_set: PreviewSet,
178
+ criteria: Optional[dict] = None,
179
+ ) -> dict:
180
+ """Compare variants within a preview set and rank them."""
181
+ criteria = criteria or {}
182
+ weight_taste = criteria.get("taste_weight", 0.3)
183
+ weight_novelty = criteria.get("novelty_weight", 0.2)
184
+ weight_identity = criteria.get("identity_weight", 0.5)
185
+
186
+ rankings = []
187
+ for v in preview_set.variants:
188
+ # Score components
189
+ taste_score = v.taste_fit
190
+ novelty_score = 1.0 - abs(v.novelty_level - 0.5) * 2 # bell curve around 0.5
191
+ identity_score = _identity_effect_score(v.identity_effect)
192
+
193
+ composite = (
194
+ taste_score * weight_taste
195
+ + novelty_score * weight_novelty
196
+ + identity_score * weight_identity
197
+ )
198
+ v.score = round(composite, 3)
199
+
200
+ rankings.append({
201
+ "variant_id": v.variant_id,
202
+ "label": v.label,
203
+ "score": v.score,
204
+ "taste_fit": v.taste_fit,
205
+ "novelty_level": v.novelty_level,
206
+ "identity_effect": v.identity_effect,
207
+ "summary": v.intent,
208
+ "what_preserved": v.what_preserved,
209
+ "why_it_matters": v.why_it_matters,
210
+ })
211
+
212
+ rankings.sort(key=lambda r: r["score"], reverse=True)
213
+
214
+ comparison = {
215
+ "rankings": rankings,
216
+ "recommended": rankings[0]["variant_id"] if rankings else "",
217
+ "criteria_used": {
218
+ "taste_weight": weight_taste,
219
+ "novelty_weight": weight_novelty,
220
+ "identity_weight": weight_identity,
221
+ },
222
+ }
223
+
224
+ preview_set.comparison = comparison
225
+ preview_set.status = "compared"
226
+ return comparison
227
+
228
+
229
+ def commit_variant(preview_set: PreviewSet, variant_id: str) -> Optional[PreviewVariant]:
230
+ """Mark a variant as committed and discard others."""
231
+ chosen = None
232
+ for v in preview_set.variants:
233
+ if v.variant_id == variant_id:
234
+ v.status = "committed"
235
+ chosen = v
236
+ else:
237
+ v.status = "discarded"
238
+
239
+ if chosen:
240
+ preview_set.committed_variant_id = variant_id
241
+ preview_set.status = "committed"
242
+
243
+ return chosen
244
+
245
+
246
+ def discard_set(set_id: str) -> bool:
247
+ """Discard an entire preview set."""
248
+ ps = _preview_sets.pop(set_id, None)
249
+ if ps:
250
+ ps.status = "discarded"
251
+ for v in ps.variants:
252
+ v.status = "discarded"
253
+ return True
254
+ return False
255
+
256
+
257
+ # ── Helpers ───────────────────────────────────────────────────────
258
+
259
+
260
+ def _compute_set_id(request_text: str, kernel_id: str) -> str:
261
+ seed = json.dumps({"request": request_text, "kernel": kernel_id}, sort_keys=True)
262
+ return "ps_" + hashlib.sha256(seed.encode()).hexdigest()[:10]
263
+
264
+
265
+ def _estimate_taste_fit(novelty: float, taste_graph: dict) -> float:
266
+ """Estimate how well a novelty level fits user taste."""
267
+ boldness = taste_graph.get("transition_boldness", 0.5)
268
+ # Users who like boldness prefer higher novelty
269
+ fit = 1.0 - abs(novelty - boldness) * 0.5
270
+ return round(max(0.0, min(1.0, fit)), 3)
271
+
272
+
273
+ def _identity_effect_score(effect: str) -> float:
274
+ """Score identity effects — preserves is safest."""
275
+ return {
276
+ "preserves": 0.9,
277
+ "evolves": 0.7,
278
+ "contrasts": 0.4,
279
+ "resets": 0.2,
280
+ }.get(effect, 0.5)
@@ -0,0 +1,74 @@
1
+ """Preview Studio data models — pure dataclasses, zero I/O."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from dataclasses import asdict, dataclass, field
7
+ from typing import Optional
8
+
9
+
10
+ @dataclass
11
+ class PreviewVariant:
12
+ """One creative option in a preview set."""
13
+
14
+ variant_id: str = ""
15
+ label: str = "" # "safe", "strong", "unexpected"
16
+ intent: str = "" # what this variant is trying to achieve
17
+ novelty_level: float = 0.0 # 0=conservative, 1=radical
18
+ songbrain_delta: str = "" # what changed vs identity
19
+ taste_fit: float = 0.5 # 0-1 how well it matches user taste
20
+ render_ref: str = "" # reference to cached render
21
+ summary: str = "" # one-line musical explanation
22
+
23
+ # What changed, why it matters, what it preserves
24
+ what_changed: str = ""
25
+ why_it_matters: str = ""
26
+ what_preserved: str = ""
27
+
28
+ # Move / plan data
29
+ move_id: str = ""
30
+ compiled_plan: Optional[dict] = None
31
+
32
+ # Scoring
33
+ score: float = 0.0
34
+ identity_effect: str = "preserves" # preserves, evolves, contrasts, resets
35
+
36
+ # State
37
+ status: str = "pending" # pending, rendered, committed, discarded
38
+ preview_mode: str = "" # audible_preview, metadata_only_preview, analytical_preview
39
+ created_at_ms: int = 0
40
+
41
+ def to_dict(self) -> dict:
42
+ d = asdict(self)
43
+ # Remove None compiled_plan for cleaner output
44
+ if d.get("compiled_plan") is None:
45
+ d.pop("compiled_plan", None)
46
+ return d
47
+
48
+
49
+ @dataclass
50
+ class PreviewSet:
51
+ """A set of variants tied to one user request."""
52
+
53
+ set_id: str = ""
54
+ request_text: str = ""
55
+ strategy: str = "creative_triptych" # creative_triptych, binary, custom
56
+ source_kernel_id: str = ""
57
+ variants: list[PreviewVariant] = field(default_factory=list)
58
+ comparison: Optional[dict] = None
59
+ committed_variant_id: str = ""
60
+ status: str = "pending" # pending, compared, committed, discarded
61
+ created_at_ms: int = field(default_factory=lambda: int(time.time() * 1000))
62
+
63
+ def to_dict(self) -> dict:
64
+ return {
65
+ "set_id": self.set_id,
66
+ "request_text": self.request_text,
67
+ "strategy": self.strategy,
68
+ "source_kernel_id": self.source_kernel_id,
69
+ "variants": [v.to_dict() for v in self.variants],
70
+ "comparison": self.comparison,
71
+ "committed_variant_id": self.committed_variant_id,
72
+ "status": self.status,
73
+ "variant_count": len(self.variants),
74
+ }