livepilot 1.21.4 → 1.22.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,114 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.0 — User atlas separation: ~/.livepilot/atlas/ (April 25 2026)
4
+
5
+ First v1.22 release. Splits the device atlas into two files that serve
6
+ different roles — a long-standing ambiguity that finally bit hard enough
7
+ to be worth fixing at minor-version scope.
8
+
9
+ ### What changed
10
+
11
+ **New: `~/.livepilot/atlas/device_atlas.json`** — the **user atlas**.
12
+ Written by `scan_full_library` on your machine. Reflects YOUR installed
13
+ packs, User Library, and plugins. Lives in the user-data directory
14
+ (same convention as `~/.livepilot/memory/`) so `npm install livepilot`
15
+ upgrades can't blow it away.
16
+
17
+ **Unchanged: `mcp_server/atlas/device_atlas.json`** — the **bundled
18
+ baseline**. Ships with the package (still 5264 devices, 120 enriched
19
+ — stock Ableton 12 Suite inventory). Gives fresh installs a functional
20
+ atlas before any personalized scan has run.
21
+
22
+ **Resolution** at load time: user atlas wins if present, else bundled
23
+ baseline falls through. Writes **always** go to the user path; the
24
+ bundled path is read-only from the scanner's perspective.
25
+
26
+ ### Why this matters
27
+
28
+ Prior to v1.22.0, the single `mcp_server/atlas/device_atlas.json` file
29
+ served three conflicting roles — repo seed, personal scan cache, and
30
+ runtime index. Three bugs resulted:
31
+
32
+ 1. **`npm install livepilot` wiped personal scans.** Every package
33
+ update overwrote the installed atlas with the bundled baseline. Users
34
+ who had carefully scanned their library (30-90 seconds per scan) lost
35
+ that work on every upgrade with no warning.
36
+ 2. **Dev installs polluted the repo.** Contributors running
37
+ LivePilot from a git checkout would scan their library (to test atlas
38
+ tools against live Ableton) and accidentally commit their personal
39
+ scan — pack names, user-library previews — to the public repo. This
40
+ happened once in the v1.21.x cycle and was the proximate trigger for
41
+ v1.22.
42
+ 3. **The "enriched" count ambiguity.** A single atlas file couldn't
43
+ honestly answer "how many devices are enriched?" — the right number
44
+ differed depending on whether you meant the bundled baseline (87 per
45
+ the scanner's last truncated pass), YAML files authored on disk (120),
46
+ or runtime coverage including native/M4L duplicates (137+ on a
47
+ fully-scanned install). The user/bundled split clarifies this by
48
+ letting each file carry its own honest count.
49
+
50
+ ### Migration for existing users
51
+
52
+ Users with a personalized scan in `mcp_server/atlas/device_atlas.json`
53
+ (most dev-install contributors) can migrate in one command:
54
+
55
+ ```bash
56
+ mkdir -p ~/.livepilot/atlas
57
+ cp "$(python3 -c 'import mcp_server.atlas; print(mcp_server.atlas.BUNDLED_ATLAS_PATH)')" \
58
+ ~/.livepilot/atlas/device_atlas.json
59
+ # Then optionally restore the bundled baseline to its shipped state:
60
+ git -C $(python3 -c 'import mcp_server.atlas, pathlib; print(pathlib.Path(mcp_server.atlas.__file__).parents[2])') \
61
+ checkout mcp_server/atlas/device_atlas.json
62
+ ```
63
+
64
+ Users on the npm-installed path (no personalized scan yet) need nothing
65
+ — the next `scan_full_library` call will write to the new user path
66
+ automatically.
67
+
68
+ ### Code changes
69
+
70
+ - `mcp_server/atlas/__init__.py` — new module-level constants
71
+ `BUNDLED_ATLAS_PATH`, `USER_ATLAS_DIR`, `USER_ATLAS_PATH`, and
72
+ function `_resolve_atlas_path()`. Existing `ATLAS_PATH` kept as a
73
+ backward-compat alias pointing at the resolved value.
74
+ - `mcp_server/atlas/tools.py::scan_full_library` — writes to
75
+ `USER_ATLAS_PATH` and creates the user-data directory on demand.
76
+ Enrichments still read from the bundled package (they're authored
77
+ in-repo under `mcp_server/atlas/enrichments/`).
78
+
79
+ ### Tests
80
+
81
+ 8 new TDD regression tests in `tests/test_atlas_user_override.py`:
82
+
83
+ - `test_bundled_path_is_in_package_dir`
84
+ - `test_user_path_is_in_home_dir`
85
+ - `test_resolver_returns_user_path_when_present`
86
+ - `test_resolver_falls_back_to_bundled_when_user_atlas_missing`
87
+ - `test_atlas_manager_loads_from_user_path_when_present`
88
+ - `test_atlas_manager_falls_back_to_bundled_when_user_atlas_missing`
89
+ - `test_scan_full_library_writes_to_user_path`
90
+ - `test_user_atlas_dir_created_if_missing`
91
+
92
+ Full suite: 3136 pass (3128 prior + 8 new), 1 skipped.
93
+
94
+ ### Documentation
95
+
96
+ - `docs/manual/getting-started.md` — new "Step 5 (optional): Personalize
97
+ the device atlas" section.
98
+ - `docs/manual/dev-install.md` — new "3a. User atlas vs bundled atlas"
99
+ subsection, critical for contributors.
100
+ - `docs/manual/device-atlas.md` — header callout on the two-file split.
101
+ - `CLAUDE.md` + `README.md` — short pointers to the new resolver.
102
+
103
+ ### Carried to future releases
104
+
105
+ The atlas schema-canonicalization originally deferred from the v1.21.2
106
+ audit is now **partially closed**: the user/bundled split makes the
107
+ "which number counts as enriched?" question honest per-file, but the
108
+ sync_metadata gate for `stats.enriched_devices` vs YAML file count
109
+ hasn't been added. Deferred to a future patch because it's orthogonal
110
+ to the user-atlas split.
111
+
3
112
  ## 1.21.4 — v1.21.2 audit carry-over: slashed-compound filler + dev-install runbook (April 25 2026)
4
113
 
5
114
  Ships two items the v1.21.2 audit #2 deferred to v1.22 but that turned
package/README.md CHANGED
@@ -105,7 +105,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
105
105
 
106
106
  **M4L Bridge** (`m4l_device/`) — Optional Max for Live Audio Effect on the master track. Provides deep LOM access through Max's LiveAPI that the ControlSurface API can't reach. UDP 9880 (M4L to server) carries spectral data and LiveAPI responses. OSC 9881 (server to M4L) sends commands. The 38 spectral/analyzer tools strictly require the bridge; device and sample tools that call the bridge also have graceful fallbacks, so core functionality works without it. Backed by 31 bridge commands for hidden parameters, Simpler internals, warp markers, display values, and Simpler warp / Compressor sidechain writes that live on child objects Python can't reach.
107
107
 
108
- **Device Atlas** (`mcp_server/atlas/`) — In-memory indexed JSON database. 5264 devices with browser URIs, 120 enriched with YAML sonic intelligence profiles (mood, genre, texture, recommended chains). 7 indexes: by_id, by_name, by_uri, by_category, by_tag, by_genre, by_pack. Reverse-index `device_techniques_index.json` powers `atlas_techniques_for_device` (146 cross-references across 58 devices). The AI never hallucinates a device name or preset — it always resolves against the atlas first.
108
+ **Device Atlas** (`mcp_server/atlas/`) — In-memory indexed JSON database. 5264 devices with browser URIs (bundled baseline), 120 enriched with YAML sonic intelligence profiles (mood, genre, texture, recommended chains). 7 indexes: by_id, by_name, by_uri, by_category, by_tag, by_genre, by_pack. Reverse-index `device_techniques_index.json` powers `atlas_techniques_for_device` (146 cross-references across 58 devices). The AI never hallucinates a device name or preset — it always resolves against the atlas first. **v1.22.0+**: run `scan_full_library` after install to index YOUR packs + User Library + plugins into `~/.livepilot/atlas/device_atlas.json` — your personal atlas overrides the baseline and survives npm updates.
109
109
 
110
110
  **Sample Engine** (`mcp_server/sample_engine/`) — Searches three sources simultaneously: BrowserSource (Ableton's library), SpliceSource (local Splice catalog via SQLite), FilesystemSource (user directories). Every result passes through a 6-critic fitness battery (key, tempo, spectral, genre, mood, technical). 29 processing techniques (Surgeon precision vs. Alchemist experimentation). Builds complete sample processing plans with warp, slice, and effect recommendations.
111
111
 
Binary file
@@ -1,2 +1,2 @@
1
1
  """LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
2
- __version__ = "1.21.4"
2
+ __version__ = "1.22.0"
@@ -519,28 +519,75 @@ class AtlasManager:
519
519
  from pathlib import Path
520
520
  from ..services.singletons import Singleton
521
521
 
522
- ATLAS_PATH = Path(__file__).parent / "device_atlas.json"
522
+ # v1.22.0: the atlas now has TWO possible homes —
523
+ #
524
+ # BUNDLED_ATLAS_PATH — mcp_server/atlas/device_atlas.json
525
+ # Ships with the package. Gives fresh installs a functional
526
+ # baseline device index before any personalized scan has run.
527
+ # Updated only when the repo's canonical baseline is regenerated.
528
+ #
529
+ # USER_ATLAS_PATH — ~/.livepilot/atlas/device_atlas.json
530
+ # Written by scan_full_library on the user's machine. Reflects
531
+ # their actual installed packs, User Library, and plugins. Lives
532
+ # in the user-data directory (same convention as
533
+ # ~/.livepilot/memory/) so npm updates can't blow it away.
534
+ #
535
+ # Resolution order at load time: user atlas wins if present, else
536
+ # bundled baseline. Written scans ALWAYS go to the user path — never
537
+ # the bundled path — so the repo/npm package stays clean regardless of
538
+ # where a user runs scan_full_library from. This split lands in v1.22.0
539
+ # and solves three prior issues (see tests/test_atlas_user_override.py
540
+ # for the full rationale).
541
+ BUNDLED_ATLAS_PATH = Path(__file__).parent / "device_atlas.json"
542
+ USER_ATLAS_DIR = Path.home() / ".livepilot" / "atlas"
543
+ USER_ATLAS_PATH = USER_ATLAS_DIR / "device_atlas.json"
544
+
545
+
546
+ def _resolve_atlas_path() -> Path:
547
+ """Return the effective atlas path — user if present, bundled if not.
548
+
549
+ Called from _build_atlas and get_atlas. Kept as a module-level
550
+ function (rather than inlined) so tests can monkeypatch HOME and
551
+ reimport the module to re-evaluate USER_ATLAS_PATH cleanly.
552
+ """
553
+ if USER_ATLAS_PATH.exists():
554
+ return USER_ATLAS_PATH
555
+ return BUNDLED_ATLAS_PATH
556
+
557
+
558
+ # Backward-compat alias. External code that imported ATLAS_PATH before
559
+ # v1.22.0 (e.g. pre-existing scripts outside this repo) gets the
560
+ # resolved path. Internal code should call _resolve_atlas_path() so the
561
+ # value is re-evaluated after the user first runs scan_full_library.
562
+ ATLAS_PATH = _resolve_atlas_path()
523
563
 
524
564
  _atlas_instance: Optional[AtlasManager] = None # kept for legacy imports
525
565
 
526
566
 
527
567
  def _build_atlas() -> AtlasManager:
528
- return AtlasManager(str(ATLAS_PATH))
568
+ return AtlasManager(str(_resolve_atlas_path()))
529
569
 
530
570
 
531
571
  _atlas_holder = Singleton(_build_atlas)
532
572
 
533
573
 
534
574
  def get_atlas() -> AtlasManager:
535
- """Thread-safe accessor. Re-reads device_atlas.json if its mtime advanced."""
575
+ """Thread-safe accessor. Re-reads the resolved atlas path if its
576
+ mtime advanced. Uses the resolver, so if the user's first
577
+ scan_full_library call runs mid-session, the next get_atlas()
578
+ picks up the new user path (via invalidate_atlas, which
579
+ scan_full_library already calls on success)."""
536
580
  global _atlas_instance
537
- instance = _atlas_holder.get(reload_if_newer=ATLAS_PATH)
581
+ instance = _atlas_holder.get(reload_if_newer=_resolve_atlas_path())
538
582
  _atlas_instance = instance # keep legacy attribute in sync
539
583
  return instance
540
584
 
541
585
 
542
586
  def invalidate_atlas() -> None:
543
- """Force the next get_atlas() to re-read device_atlas.json from disk."""
587
+ """Force the next get_atlas() to re-read the resolved atlas path
588
+ from disk. Called by scan_full_library after writing a fresh
589
+ user atlas so subsequent tool calls see the new data without
590
+ an MCP server restart."""
544
591
  global _atlas_instance
545
592
  _atlas_holder.invalidate()
546
593
  _atlas_instance = None
@@ -466,11 +466,17 @@ def scan_full_library(
466
466
  """
467
467
  from .scanner import normalize_scan_results
468
468
  from .enrichments import load_enrichments, merge_enrichments
469
- from . import AtlasManager
469
+ from . import AtlasManager, USER_ATLAS_DIR, USER_ATLAS_PATH
470
470
 
471
+ # v1.22.0: scans always write to the user atlas path, never the
472
+ # bundled baseline. The user-data directory is created on demand
473
+ # so a brand-new install (no ~/.livepilot/ at all) still works.
474
+ # Enrichments are read from the bundled package (same as before —
475
+ # they're authored in-repo).
471
476
  atlas_dir = os.path.dirname(os.path.abspath(__file__))
472
- atlas_path = os.path.join(atlas_dir, "device_atlas.json")
473
477
  enrichments_dir = os.path.join(atlas_dir, "enrichments")
478
+ USER_ATLAS_DIR.mkdir(parents=True, exist_ok=True)
479
+ atlas_path = str(USER_ATLAS_PATH)
474
480
 
475
481
  if not force and os.path.exists(atlas_path):
476
482
  age = time.time() - os.path.getmtime(atlas_path)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.21.4",
3
+ "version": "1.22.0",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
5
  "description": "Agentic production system for Ableton Live 12 — 430 tools, 53 domains, 44 semantic moves. Device atlas (5264 devices, 120 enriched, 7 indexes), Splice intelligence (gRPC + GraphQL describe-a-sound + preview + collections + presets), 9-band spectral perception auto-loaded via ensure_analyzer_on_master, Creative Director skill, technique memory, 12 creative intelligence engines",
6
6
  "author": "Pilot Studio",
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
5
5
  when this script is selected in Preferences > Link, Tempo & MIDI.
6
6
  """
7
7
 
8
- __version__ = "1.21.4"
8
+ __version__ = "1.22.0"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from . import router
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/dreamrec/LivePilot",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.21.4",
9
+ "version": "1.22.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.21.4",
14
+ "version": "1.22.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }