-
-
Notifications
You must be signed in to change notification settings - Fork 6
193 lines (186 loc) · 9.3 KB
/
Copy pathci.yml
File metadata and controls
193 lines (186 loc) · 9.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
python-tests:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# 3.12 is the MCP-server floor: scipy>=1.18 dropped 3.11 (numpy is
# currently capped <2.5 for numba/librosa — see requirements.txt).
# NOTE: the Remote Script runs inside Ableton's embedded Python (3.11 on
# Live 12.3); it must stay 3.11-syntax-compatible even though CI now
# exercises only 3.12. Add a 3.11 remote_script-only job if that drifts.
python-version: ["3.12"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
- run: |
pip install numpy setuptools wheel
pip install -r requirements.txt pytest pytest-asyncio
- run: pytest tests/ -v
metadata-drift:
# Enforces that every file listed in CLAUDE.md as version/count-bearing is
# actually in sync. Prior releases (1.10.2, 1.10.4, 1.10.6) each shipped
# with some form of metadata drift because this check was manual only.
# No untrusted inputs: sync_metadata.py is a repo-local script.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
- name: Install dependencies
# The domain-map check regenerates docs/manual/index.md's table from
# the LIVE tool registry (mcp.list_tools()), which imports
# mcp_server.server — fastmcp and friends must be importable here.
run: |
pip install numpy setuptools wheel
pip install -r requirements.txt
- name: sync_metadata --check
run: python scripts/sync_metadata.py --check
amxd-freeze-drift:
# Guard against the repeating "bridge ping reports old version" bug: if
# the frozen .amxd embeds an older version string than the repo, refuse
# to merge. Two prior releases were lost to this class of drift.
# No untrusted inputs: VERSION is derived from the repo's own code.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: .amxd ping version matches repo version
run: |
VERSION=$(python -c "import sys; sys.path.insert(0,'.'); from mcp_server import __version__; print(__version__)")
echo "Repo version: $VERSION"
# v1.23.3: grep the source-of-truth `var VERSION = "X.Y.Z"` literal
# which is always emitted by Max during freeze, instead of the legacy
# JSON form `"version": "X.Y.Z"`. The legacy form was the runtime
# ping response value back when bridge.js hardcoded version strings;
# since the v1.20-era refactor to `"version": VERSION`, the JSON
# literal only appeared via post-export binary-patch. The
# `var VERSION` form is in bridge.js source and survives every
# Max freeze — no patching needed.
if ! strings m4l_device/LivePilot_Analyzer.amxd | grep -qE "var VERSION[[:space:]]*=[[:space:]]*\"$VERSION\""; then
echo "::error::LivePilot_Analyzer.amxd does NOT embed 'var VERSION = \"$VERSION\"'."
echo "::error::Bridge.js source and .amxd freeze are out of sync."
echo "::error::Fix: bump 'var VERSION' in m4l_device/livepilot_bridge.js,"
echo "::error::sync to '~/Documents/Max 9/Max for Live Devices/LivePilot_Analyzer Project/code/livepilot_bridge.js',"
echo "::error::open m4l_device/LivePilot_Analyzer.maxpat in Max, Cmd-S to re-export."
exit 1
fi
echo ".amxd embeds 'var VERSION = \"$VERSION\"' — source/freeze in sync."
- name: .amxd embeds every bridge command from livepilot_bridge.js
# The version-string check alone can be satisfied by the documented
# 6-byte binary patch after real JS logic changes. Frozen .amxd files
# embed the JS source as plain text, so diff the full set of
# `case "<command>":` names from source against the binary's strings.
# No untrusted inputs — both files are repo-local.
run: |
python3 - <<'PYEOF'
import re, subprocess, sys
src = open("m4l_device/livepilot_bridge.js", encoding="utf-8").read()
cmds = sorted(set(re.findall(r'case\s*"([^"]+)"\s*:', src)))
if not cmds:
print("::error::No case \"...\" commands parsed from livepilot_bridge.js")
sys.exit(1)
blob = subprocess.check_output(["strings", "m4l_device/LivePilot_Analyzer.amxd"], text=True)
missing = [c for c in cmds if f'case "{c}"' not in blob]
if missing:
print(f"::error::.amxd freeze is stale — {len(missing)} bridge command(s) in "
f"livepilot_bridge.js are missing from the frozen binary: {missing}")
print("::error::A real Max re-freeze is required (binary version patch is not enough).")
sys.exit(1)
print(f"All {len(cmds)} bridge commands present in the frozen .amxd.")
PYEOF
- name: Full freeze-chain verification
# Superset of the two checks above. The `case "<command>":` diff cannot see a batch that
# adds no NEW command — the v1.28 token auth wraps the existing dispatcher, so a binary
# containing none of its code still passes that check. This script additionally diffs
# top-level JS identifiers against the binary, and asserts the freeze is still FAT.
# No untrusted inputs — repo-local files only. Install targets are absent on CI and skip.
run: bash scripts/sync_amxd_targets.sh
remote-script-py311:
# The Remote Script runs inside Ableton's embedded Python (3.11 on Live
# 12.3/12.4) while the MCP-server floor is 3.12. Nothing else enforces
# that remote_script/ stays 3.11-compatible — this job does.
# No untrusted inputs: compiles repo-local code only.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.11"
- name: remote_script compiles under Python 3.11
run: python -m compileall -q remote_script/LivePilot
- name: vermin — no >3.11 stdlib/API usage in remote_script
run: |
pip install --quiet vermin
vermin --target=3.11- --violations --no-tips remote_script/LivePilot
mcpb-build:
# build_mcpb.sh produces the release asset that silently shipped missing
# for four consecutive releases (v1.17–v1.20.2). Build it on every push
# so a broken script/manifest is caught at PR time, not release time.
# No untrusted inputs, no publishing, no secrets.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Build MCPB bundle
run: bash scripts/build_mcpb.sh
- name: Assert bundle exists and is non-trivial
run: |
BUNDLE=$(ls dist/livepilot-*.mcpb 2>/dev/null | head -1)
if [ -z "$BUNDLE" ]; then
echo "::error::build_mcpb.sh produced no dist/livepilot-*.mcpb"
exit 1
fi
SIZE=$(wc -c < "$BUNDLE")
echo "Built $BUNDLE ($SIZE bytes)"
if [ "$SIZE" -lt 1000000 ]; then
echo "::error::MCPB bundle is suspiciously small (<1MB) — staging likely broke"
exit 1
fi
js-entrypoint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: "22"
- name: Verify JS entrypoint parses and --help works
run: |
node -c bin/livepilot.js
node bin/livepilot.js --help
- name: Verify npm pack includes expected files
run: |
npm pack --dry-run 2>&1 | tee /dev/stderr | grep -q "livepilot_bridge.js"
npm pack --dry-run 2>&1 | grep -q "LivePilot_Analyzer.amxd"
npm pack --dry-run 2>&1 | grep -q "remote_script/LivePilot/__init__.py"
npm pack --dry-run 2>&1 | grep -q "livepilot/skills/livepilot-core/SKILL.md"
npm pack --dry-run 2>&1 | grep -q "livepilot/.Codex-plugin/plugin.json"
- name: Verify npm pack excludes dirty/local files
# Prevents shipping local-only artifacts like .mcp.json.disabled (with
# absolute workstation paths), pre-freeze backups, swap files, or
# .DS_Store. No untrusted inputs — static grep against pack output.
run: |
npm pack --dry-run 2>&1 | tee pack.log
if grep -E '\.(disabled|backup|bak|swp|orig)$|\.pre-|\.DS_Store' pack.log; then
echo "::error::npm pack contains dirty/local artifacts. Exclude them via the \"files\" allowlist in package.json or .npmignore."
exit 1
fi
if grep -E 'plugins-synths\.md|synths-m4l\.md|utility-and-workflow\.md|samples-and-irs\.md|presets-by-vibe\.md|m4l-vendor|m4l-depth-pass|m4l-library-deep|m4l-master-reference|m4l-technique-map' pack.log; then
echo "::error::npm pack contains personal/user atlas files. Keep these gitignored files out of the npm payload."
exit 1
fi