livepilot 1.10.4 → 1.10.6

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 (74) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +3 -3
  3. package/CHANGELOG.md +148 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +6 -6
  6. package/livepilot/.Codex-plugin/plugin.json +2 -2
  7. package/livepilot/.claude-plugin/plugin.json +2 -2
  8. package/livepilot/skills/livepilot-core/SKILL.md +4 -4
  9. package/livepilot/skills/livepilot-core/references/overview.md +3 -3
  10. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  11. package/livepilot/skills/livepilot-release/SKILL.md +5 -5
  12. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  13. package/m4l_device/livepilot_bridge.js +12 -1
  14. package/manifest.json +3 -3
  15. package/mcp_server/__init__.py +1 -1
  16. package/mcp_server/composer/sample_resolver.py +10 -6
  17. package/mcp_server/composer/tools.py +10 -6
  18. package/mcp_server/connection.py +6 -1
  19. package/mcp_server/creative_constraints/tools.py +9 -8
  20. package/mcp_server/experiment/engine.py +9 -5
  21. package/mcp_server/experiment/tools.py +9 -9
  22. package/mcp_server/hook_hunter/tools.py +14 -9
  23. package/mcp_server/m4l_bridge.py +11 -0
  24. package/mcp_server/memory/taste_graph.py +7 -2
  25. package/mcp_server/mix_engine/tools.py +8 -3
  26. package/mcp_server/musical_intelligence/tools.py +15 -10
  27. package/mcp_server/performance_engine/tools.py +6 -2
  28. package/mcp_server/preview_studio/tools.py +21 -15
  29. package/mcp_server/project_brain/tools.py +18 -10
  30. package/mcp_server/reference_engine/tools.py +7 -5
  31. package/mcp_server/runtime/capability_probe.py +10 -4
  32. package/mcp_server/runtime/tools.py +8 -2
  33. package/mcp_server/sample_engine/tools.py +394 -33
  34. package/mcp_server/semantic_moves/tools.py +5 -1
  35. package/mcp_server/server.py +10 -9
  36. package/mcp_server/services/motif_service.py +9 -3
  37. package/mcp_server/session_continuity/tools.py +7 -3
  38. package/mcp_server/session_continuity/tracker.py +9 -8
  39. package/mcp_server/song_brain/tools.py +17 -12
  40. package/mcp_server/splice_client/client.py +19 -6
  41. package/mcp_server/stuckness_detector/tools.py +8 -5
  42. package/mcp_server/tools/_agent_os_engine/__init__.py +52 -0
  43. package/mcp_server/tools/_agent_os_engine/critics.py +134 -0
  44. package/mcp_server/tools/_agent_os_engine/evaluation.py +206 -0
  45. package/mcp_server/tools/_agent_os_engine/models.py +132 -0
  46. package/mcp_server/tools/_agent_os_engine/taste.py +192 -0
  47. package/mcp_server/tools/_agent_os_engine/techniques.py +161 -0
  48. package/mcp_server/tools/_agent_os_engine/world_model.py +170 -0
  49. package/mcp_server/tools/_composition_engine/__init__.py +67 -0
  50. package/mcp_server/tools/_composition_engine/analysis.py +174 -0
  51. package/mcp_server/tools/_composition_engine/critics.py +522 -0
  52. package/mcp_server/tools/_composition_engine/gestures.py +230 -0
  53. package/mcp_server/tools/_composition_engine/harmony.py +70 -0
  54. package/mcp_server/tools/_composition_engine/models.py +193 -0
  55. package/mcp_server/tools/_composition_engine/sections.py +371 -0
  56. package/mcp_server/tools/_perception_engine.py +18 -11
  57. package/mcp_server/tools/agent_os.py +23 -15
  58. package/mcp_server/tools/analyzer.py +166 -7
  59. package/mcp_server/tools/automation.py +6 -1
  60. package/mcp_server/tools/composition.py +25 -16
  61. package/mcp_server/tools/devices.py +10 -6
  62. package/mcp_server/tools/motif.py +7 -2
  63. package/mcp_server/tools/planner.py +6 -2
  64. package/mcp_server/tools/research.py +13 -10
  65. package/mcp_server/transition_engine/tools.py +6 -1
  66. package/mcp_server/translation_engine/tools.py +8 -6
  67. package/mcp_server/wonder_mode/engine.py +8 -3
  68. package/mcp_server/wonder_mode/tools.py +29 -21
  69. package/package.json +2 -2
  70. package/remote_script/LivePilot/__init__.py +1 -1
  71. package/requirements.txt +6 -0
  72. package/livepilot.mcpb +0 -0
  73. package/mcp_server/tools/_agent_os_engine.py +0 -947
  74. package/mcp_server/tools/_composition_engine.py +0 -1530
@@ -0,0 +1,67 @@
1
+ """Composition engine — structural + musical intelligence for arrangement.
2
+
3
+ This package replaces the former single-file `_composition_engine.py`.
4
+ The public surface is unchanged — callers import the same names:
5
+
6
+ from mcp_server.tools._composition_engine import (
7
+ SectionType, RoleType, GestureIntent,
8
+ build_section_graph_from_scenes, detect_phrases,
9
+ run_form_critic, plan_gesture, ...,
10
+ )
11
+
12
+ Internal organization:
13
+ models.py — Enums and dataclasses (shared types)
14
+ sections.py — Section graph, phrase grid, role inference
15
+ critics.py — Form / identity / phrase / transition / emotional / cross-section
16
+ gestures.py — Gesture planner and template library
17
+ harmony.py — Harmony field construction
18
+ analysis.py — Composition evaluation, outcomes, taste model, constants
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from .models import (
23
+ SectionType, RoleType, GestureIntent,
24
+ SectionNode, PhraseUnit, RoleNode, CompositionIssue,
25
+ GesturePlan, CompositionAnalysis, HarmonyField,
26
+ )
27
+ from .sections import (
28
+ build_section_graph_from_scenes,
29
+ build_section_graph_from_arrangement,
30
+ detect_phrases,
31
+ infer_role_for_track,
32
+ build_role_graph,
33
+ )
34
+ from .critics import (
35
+ run_form_critic,
36
+ run_section_identity_critic,
37
+ run_phrase_critic,
38
+ run_transition_critic,
39
+ run_emotional_arc_critic,
40
+ run_cross_section_critic,
41
+ )
42
+ from .gestures import (
43
+ GESTURE_TEMPLATES,
44
+ plan_gesture,
45
+ resolve_gesture_template,
46
+ )
47
+ from .harmony import build_harmony_field
48
+ from .analysis import (
49
+ COMPOSITION_DIMENSIONS,
50
+ analyze_section_outcomes,
51
+ evaluate_composition_move,
52
+ build_composition_taste_model,
53
+ )
54
+
55
+ __all__ = [
56
+ "SectionType", "RoleType", "GestureIntent",
57
+ "SectionNode", "PhraseUnit", "RoleNode", "CompositionIssue",
58
+ "GesturePlan", "CompositionAnalysis", "HarmonyField",
59
+ "build_section_graph_from_scenes", "build_section_graph_from_arrangement",
60
+ "detect_phrases", "infer_role_for_track", "build_role_graph",
61
+ "run_form_critic", "run_section_identity_critic", "run_phrase_critic",
62
+ "run_transition_critic", "run_emotional_arc_critic", "run_cross_section_critic",
63
+ "GESTURE_TEMPLATES", "plan_gesture", "resolve_gesture_template",
64
+ "build_harmony_field",
65
+ "COMPOSITION_DIMENSIONS", "analyze_section_outcomes",
66
+ "evaluate_composition_move", "build_composition_taste_model",
67
+ ]
@@ -0,0 +1,174 @@
1
+ """Part of the _composition_engine package — extracted from the single-file engine.
2
+
3
+ Pure-computation core, no external deps. Callers should import from the package
4
+ facade (e.g. `from mcp_server.tools._composition_engine import X`), which
5
+ re-exports everything from these sub-modules.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ import re
11
+ from dataclasses import asdict, dataclass, field
12
+ from enum import Enum
13
+ from typing import Any, Optional
14
+
15
+ from .models import SectionNode, CompositionIssue, CompositionAnalysis
16
+
17
+
18
+ # ── Section Outcome Analysis (Round 2) ────────────────────────────────
19
+ def analyze_section_outcomes(
20
+ outcomes: list[dict],
21
+ ) -> dict:
22
+ """Analyze composition outcomes grouped by section type.
23
+
24
+ outcomes: list of composition_outcome payloads
25
+ Returns: {section_type: {move_name: {avg_score, count, keep_rate}}}
26
+ """
27
+ by_section: dict[str, list[dict]] = {}
28
+
29
+ for o in outcomes:
30
+ section_type = o.get("section_type", "unknown")
31
+ by_section.setdefault(section_type, []).append(o)
32
+
33
+ result = {}
34
+ for stype, section_outcomes in by_section.items():
35
+ move_stats: dict[str, dict] = {}
36
+ for o in section_outcomes:
37
+ move = o.get("move_name", "unknown")
38
+ stats = move_stats.setdefault(move, {"scores": [], "kept": 0, "total": 0})
39
+ stats["scores"].append(o.get("score", 0))
40
+ stats["total"] += 1
41
+ if o.get("kept", False):
42
+ stats["kept"] += 1
43
+
44
+ result[stype] = {
45
+ move: {
46
+ "avg_score": round(sum(s["scores"]) / len(s["scores"]), 3) if s["scores"] else 0,
47
+ "count": s["total"],
48
+ "keep_rate": round(s["kept"] / s["total"], 3) if s["total"] > 0 else 0,
49
+ }
50
+ for move, s in move_stats.items()
51
+ }
52
+
53
+ return {
54
+ "section_types": list(result.keys()),
55
+ "outcomes_by_section": result,
56
+ "total_outcomes": sum(len(v) for v in by_section.values()),
57
+ }
58
+
59
+
60
+ # ── Composition Evaluation ────────────────────────────────────────────
61
+ COMPOSITION_DIMENSIONS = frozenset({
62
+ "section_clarity", "phrase_completion", "narrative_pacing",
63
+ "transition_strength", "orchestration_clarity", "tension_release",
64
+ })
65
+
66
+ def evaluate_composition_move(
67
+ before_issues: list[CompositionIssue],
68
+ after_issues: list[CompositionIssue],
69
+ target_dimensions: dict[str, float],
70
+ protect: dict[str, float],
71
+ ) -> dict:
72
+ """Evaluate whether a composition move improved the arrangement.
73
+
74
+ Compares issue counts and severities before and after.
75
+ Returns: {score, keep_change, issue_delta, notes}
76
+ """
77
+ notes: list[str] = []
78
+
79
+ # Count issues by type before and after
80
+ before_count = len(before_issues)
81
+ after_count = len(after_issues)
82
+ issue_delta = before_count - after_count
83
+
84
+ # Severity-weighted improvement
85
+ before_severity = sum(i.severity for i in before_issues)
86
+ after_severity = sum(i.severity for i in after_issues)
87
+ severity_improvement = before_severity - after_severity
88
+
89
+ # Score: positive improvement = good
90
+ if before_count > 0:
91
+ improvement_ratio = severity_improvement / max(before_severity, 0.01)
92
+ else:
93
+ improvement_ratio = 0.0 if after_count == 0 else -0.5
94
+
95
+ # Normalize to 0-1 score
96
+ score = max(0.0, min(1.0, 0.5 + improvement_ratio * 0.5))
97
+
98
+ # Keep/undo decision
99
+ keep_change = True
100
+
101
+ if severity_improvement < 0:
102
+ keep_change = False
103
+ notes.append(f"WORSE: total severity increased by {-severity_improvement:.2f}")
104
+
105
+ if after_count > before_count + 1:
106
+ keep_change = False
107
+ notes.append(f"NEW ISSUES: {after_count - before_count} new issues introduced")
108
+
109
+ if score < 0.40:
110
+ keep_change = False
111
+ notes.append(f"SCORE: {score:.3f} below 0.40 threshold")
112
+
113
+ if keep_change and severity_improvement > 0:
114
+ notes.append(f"IMPROVED: resolved {issue_delta} issue(s), severity reduced by {severity_improvement:.2f}")
115
+
116
+ return {
117
+ "score": round(score, 4),
118
+ "keep_change": keep_change,
119
+ "issue_delta": issue_delta,
120
+ "before_issue_count": before_count,
121
+ "after_issue_count": after_count,
122
+ "severity_improvement": round(severity_improvement, 4),
123
+ "notes": notes,
124
+ "consecutive_undo_hint": not keep_change,
125
+ }
126
+
127
+
128
+ # ── Composition Taste Model (Round 4) ───────────────────────────────
129
+ def build_composition_taste_model(
130
+ section_outcomes: list[dict],
131
+ ) -> dict:
132
+ """Build per-section-type preferences from composition outcome history.
133
+
134
+ Aggregates section outcomes to learn: what density, foreground count,
135
+ and move types does this user prefer for each section type?
136
+
137
+ Returns: {section_type: {preferred_density, preferred_foreground_count,
138
+ top_moves, sample_size}}
139
+ """
140
+ if not section_outcomes:
141
+ return {"section_types": {}, "sample_size": 0}
142
+
143
+ by_type: dict[str, list[dict]] = {}
144
+ for o in section_outcomes:
145
+ stype = o.get("section_type", "unknown")
146
+ by_type.setdefault(stype, []).append(o)
147
+
148
+ preferences: dict[str, dict] = {}
149
+ for stype, outcomes in by_type.items():
150
+ kept = [o for o in outcomes if o.get("kept", False)]
151
+ densities = [o.get("density", 0.5) for o in kept if "density" in o]
152
+ fg_counts = [o.get("foreground_count", 1) for o in kept if "foreground_count" in o]
153
+
154
+ # Tally move types
155
+ move_counts: dict[str, int] = {}
156
+ for o in kept:
157
+ move = o.get("move_name", "unknown")
158
+ move_counts[move] = move_counts.get(move, 0) + 1
159
+
160
+ top_moves = sorted(move_counts.items(), key=lambda x: -x[1])[:3]
161
+
162
+ preferences[stype] = {
163
+ "preferred_density": round(sum(densities) / len(densities), 2) if densities else 0.5,
164
+ "preferred_foreground_count": round(sum(fg_counts) / len(fg_counts), 1) if fg_counts else 1.0,
165
+ "top_moves": [{"move": m, "count": c} for m, c in top_moves],
166
+ "keep_rate": round(len(kept) / len(outcomes), 3) if outcomes else 0,
167
+ "sample_size": len(outcomes),
168
+ }
169
+
170
+ return {
171
+ "section_types": preferences,
172
+ "sample_size": sum(len(v) for v in by_type.values()),
173
+ }
174
+