livepilot 1.21.4 → 1.22.1

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,176 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.1 — Bundled enrichment coverage gate (April 25 2026)
4
+
5
+ Closes the one item carried from v1.22.0's atlas-separation work: a
6
+ visibility + soft-gate for the drift between the atlas file's
7
+ self-reported enrichment count and the YAML files on disk.
8
+
9
+ ### What changed
10
+
11
+ Two enrichment numbers now surface in `sync_metadata --check`:
12
+
13
+ - **`enriched=N`** (existing) — YAML profiles authored in `mcp_server/atlas/enrichments/`. Measures "what's available for merge."
14
+ - **`bundled_enriched=N`** (new) — `stats.enriched_devices` from the shipped `mcp_server/atlas/device_atlas.json`. Measures "what the last scan_full_library run actually applied at build time."
15
+
16
+ These measure different things. YAML count is authoring effort; bundled
17
+ count is runtime coverage as of the atlas's last regeneration. They
18
+ drift naturally (someone adds a YAML without re-scanning) — but until
19
+ v1.22.1 the drift was invisible to CI.
20
+
21
+ ### Soft gate
22
+
23
+ Warns (doesn't fail) on two conditions:
24
+
25
+ 1. **`bundled_enriched == 0`** with YAMLs on disk — scanner never ran
26
+ or failed completely. Most likely the repo's bundled atlas got
27
+ accidentally emptied or mis-committed.
28
+ 2. **`bundled_enriched / yaml_count < 50%`** — scanner truncated or had
29
+ severe pack-coverage failures. Current shipped atlas is 87/120 = 72%
30
+ coverage (healthy — the 33 orphan gap is the miditool-domain YAMLs
31
+ that Live's browser scanner can't see).
32
+
33
+ Why soft: the relationship `yaml >= bundled` is only true in
34
+ single-pack-scan scenarios. Multi-category duplication (native +
35
+ max_for_live + user_library for the same device_id) can push
36
+ `bundled > yaml`. Strict equality would produce false alarms. The soft
37
+ gate catches the two real failure modes while staying silent on healthy
38
+ cases.
39
+
40
+ ### Output format
41
+
42
+ ```
43
+ Source of truth: version=1.22.1, tools=430, domains=53, bridge_cmds=31,
44
+ enriched=120, bundled_enriched=87, genres=4, moves=44,
45
+ analyzer_tools=38, atlas_devices=5264
46
+ ```
47
+
48
+ Warnings (if any) print above the fail/pass line with a ⚠️ header,
49
+ separate from the issue list. The exit code is unchanged — warnings
50
+ don't fail CI.
51
+
52
+ ### Tests
53
+
54
+ 7 new TDD tests in `tests/test_claim_consistency.py`. Full suite: 3143
55
+ pass (3136 prior + 7 new), 1 skipped.
56
+
57
+ ### Why this is a patch, not a feature
58
+
59
+ Pure CI-gate tightening. Zero user-visible runtime behavior change;
60
+ the only observable delta is one additional field in the banner plus
61
+ the possibility of a soft-warning line during `sync_metadata --check`.
62
+ No new tools, no atlas behavior change. The v1.22.0 user/bundled split
63
+ is the feature release; v1.22.1 is its mechanical follow-through.
64
+
65
+ ## 1.22.0 — User atlas separation: ~/.livepilot/atlas/ (April 25 2026)
66
+
67
+ First v1.22 release. Splits the device atlas into two files that serve
68
+ different roles — a long-standing ambiguity that finally bit hard enough
69
+ to be worth fixing at minor-version scope.
70
+
71
+ ### What changed
72
+
73
+ **New: `~/.livepilot/atlas/device_atlas.json`** — the **user atlas**.
74
+ Written by `scan_full_library` on your machine. Reflects YOUR installed
75
+ packs, User Library, and plugins. Lives in the user-data directory
76
+ (same convention as `~/.livepilot/memory/`) so `npm install livepilot`
77
+ upgrades can't blow it away.
78
+
79
+ **Unchanged: `mcp_server/atlas/device_atlas.json`** — the **bundled
80
+ baseline**. Ships with the package (still 5264 devices, 120 enriched
81
+ — stock Ableton 12 Suite inventory). Gives fresh installs a functional
82
+ atlas before any personalized scan has run.
83
+
84
+ **Resolution** at load time: user atlas wins if present, else bundled
85
+ baseline falls through. Writes **always** go to the user path; the
86
+ bundled path is read-only from the scanner's perspective.
87
+
88
+ ### Why this matters
89
+
90
+ Prior to v1.22.0, the single `mcp_server/atlas/device_atlas.json` file
91
+ served three conflicting roles — repo seed, personal scan cache, and
92
+ runtime index. Three bugs resulted:
93
+
94
+ 1. **`npm install livepilot` wiped personal scans.** Every package
95
+ update overwrote the installed atlas with the bundled baseline. Users
96
+ who had carefully scanned their library (30-90 seconds per scan) lost
97
+ that work on every upgrade with no warning.
98
+ 2. **Dev installs polluted the repo.** Contributors running
99
+ LivePilot from a git checkout would scan their library (to test atlas
100
+ tools against live Ableton) and accidentally commit their personal
101
+ scan — pack names, user-library previews — to the public repo. This
102
+ happened once in the v1.21.x cycle and was the proximate trigger for
103
+ v1.22.
104
+ 3. **The "enriched" count ambiguity.** A single atlas file couldn't
105
+ honestly answer "how many devices are enriched?" — the right number
106
+ differed depending on whether you meant the bundled baseline (87 per
107
+ the scanner's last truncated pass), YAML files authored on disk (120),
108
+ or runtime coverage including native/M4L duplicates (137+ on a
109
+ fully-scanned install). The user/bundled split clarifies this by
110
+ letting each file carry its own honest count.
111
+
112
+ ### Migration for existing users
113
+
114
+ Users with a personalized scan in `mcp_server/atlas/device_atlas.json`
115
+ (most dev-install contributors) can migrate in one command:
116
+
117
+ ```bash
118
+ mkdir -p ~/.livepilot/atlas
119
+ cp "$(python3 -c 'import mcp_server.atlas; print(mcp_server.atlas.BUNDLED_ATLAS_PATH)')" \
120
+ ~/.livepilot/atlas/device_atlas.json
121
+ # Then optionally restore the bundled baseline to its shipped state:
122
+ git -C $(python3 -c 'import mcp_server.atlas, pathlib; print(pathlib.Path(mcp_server.atlas.__file__).parents[2])') \
123
+ checkout mcp_server/atlas/device_atlas.json
124
+ ```
125
+
126
+ Users on the npm-installed path (no personalized scan yet) need nothing
127
+ — the next `scan_full_library` call will write to the new user path
128
+ automatically.
129
+
130
+ ### Code changes
131
+
132
+ - `mcp_server/atlas/__init__.py` — new module-level constants
133
+ `BUNDLED_ATLAS_PATH`, `USER_ATLAS_DIR`, `USER_ATLAS_PATH`, and
134
+ function `_resolve_atlas_path()`. Existing `ATLAS_PATH` kept as a
135
+ backward-compat alias pointing at the resolved value.
136
+ - `mcp_server/atlas/tools.py::scan_full_library` — writes to
137
+ `USER_ATLAS_PATH` and creates the user-data directory on demand.
138
+ Enrichments still read from the bundled package (they're authored
139
+ in-repo under `mcp_server/atlas/enrichments/`).
140
+
141
+ ### Tests
142
+
143
+ 8 new TDD regression tests in `tests/test_atlas_user_override.py`:
144
+
145
+ - `test_bundled_path_is_in_package_dir`
146
+ - `test_user_path_is_in_home_dir`
147
+ - `test_resolver_returns_user_path_when_present`
148
+ - `test_resolver_falls_back_to_bundled_when_user_atlas_missing`
149
+ - `test_atlas_manager_loads_from_user_path_when_present`
150
+ - `test_atlas_manager_falls_back_to_bundled_when_user_atlas_missing`
151
+ - `test_scan_full_library_writes_to_user_path`
152
+ - `test_user_atlas_dir_created_if_missing`
153
+
154
+ Full suite: 3136 pass (3128 prior + 8 new), 1 skipped.
155
+
156
+ ### Documentation
157
+
158
+ - `docs/manual/getting-started.md` — new "Step 5 (optional): Personalize
159
+ the device atlas" section.
160
+ - `docs/manual/dev-install.md` — new "3a. User atlas vs bundled atlas"
161
+ subsection, critical for contributors.
162
+ - `docs/manual/device-atlas.md` — header callout on the two-file split.
163
+ - `CLAUDE.md` + `README.md` — short pointers to the new resolver.
164
+
165
+ ### Carried to future releases
166
+
167
+ The atlas schema-canonicalization originally deferred from the v1.21.2
168
+ audit is now **partially closed**: the user/bundled split makes the
169
+ "which number counts as enriched?" question honest per-file, but the
170
+ sync_metadata gate for `stats.enriched_devices` vs YAML file count
171
+ hasn't been added. Deferred to a future patch because it's orthogonal
172
+ to the user-atlas split.
173
+
3
174
  ## 1.21.4 — v1.21.2 audit carry-over: slashed-compound filler + dev-install runbook (April 25 2026)
4
175
 
5
176
  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.1"
@@ -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.1",
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.1"
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.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.21.4",
14
+ "version": "1.22.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }