@mthines/reaper-mcp 0.19.0 → 0.70.2

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 (116) hide show
  1. package/README.md +19 -570
  2. package/bridge-ui/mcp_bridge.html +129 -0
  3. package/bridge-ui/mcp_bridge.webmanifest +16 -0
  4. package/bridge-ui/mcp_bridge_icon_192.png +0 -0
  5. package/bridge-ui/mcp_bridge_icon_512.png +0 -0
  6. package/bridge-ui/mcp_bridge_sw.js +35 -0
  7. package/claude/agents/plugin-researcher.md +188 -0
  8. package/claude/knowledge/reference/aesthetics-scoring.md +48 -0
  9. package/{knowledge → claude/knowledge}/reference/common-mistakes.md +1 -1
  10. package/claude/knowledge/reference/lorekit-setup.md +36 -0
  11. package/claude/knowledge/reference/memory-protocol.md +377 -0
  12. package/{knowledge → claude/knowledge}/reference/metering.md +5 -5
  13. package/claude/knowledge/reference/perceived-loudness.md +182 -0
  14. package/{knowledge → claude/knowledge}/workflows/editing.md +4 -0
  15. package/{knowledge → claude/knowledge}/workflows/gain-staging.md +21 -23
  16. package/{knowledge → claude/knowledge}/workflows/learn-plugin.md +15 -48
  17. package/{knowledge → claude/knowledge}/workflows/master-bus.md +13 -1
  18. package/claude/knowledge/workflows/section-levels.md +138 -0
  19. package/{knowledge → claude/knowledge}/workflows/session-prep.md +4 -0
  20. package/claude/knowledge/workflows/transient-lighting.md +52 -0
  21. package/claude/skills/critique/SKILL.md +131 -0
  22. package/claude/skills/editing/SKILL.md +281 -0
  23. package/claude/skills/learn-plugin/SKILL.md +210 -0
  24. package/claude/skills/mastering/SKILL.md +151 -0
  25. package/claude/skills/mix-memory/SKILL.md +140 -0
  26. package/claude/skills/mixer/SKILL.md +253 -0
  27. package/claude/skills/new-extension/SKILL.md +324 -0
  28. package/main.js +7843 -1940
  29. package/package.json +17 -15
  30. package/reaper/CLAUDE.md +22 -4
  31. package/reaper/mcp_actions_lib.lua +375 -0
  32. package/reaper/mcp_analyzer.jsfx +27 -12
  33. package/reaper/mcp_arrange_lib.lua +949 -0
  34. package/reaper/mcp_bridge.lua +2321 -758
  35. package/reaper/mcp_edit_views_lib.lua +1378 -0
  36. package/reaper/mcp_envelopes_lib.lua +748 -0
  37. package/reaper/mcp_extensions_lib.lua +55 -0
  38. package/reaper/mcp_fxstate_lib.lua +144 -0
  39. package/reaper/mcp_gain_trace.jsfx +126 -0
  40. package/reaper/mcp_items_lib.lua +877 -0
  41. package/reaper/mcp_lufs_meter.jsfx +16 -13
  42. package/reaper/mcp_midi_lib.lua +555 -0
  43. package/reaper/mcp_mixing_lib.lua +1021 -0
  44. package/reaper/mcp_plan_lib.lua +801 -0
  45. package/reaper/mcp_prefs_lib.lua +127 -0
  46. package/reaper/mcp_project_lib.lua +1088 -0
  47. package/reaper/mcp_recording_lib.lua +595 -0
  48. package/reaper/mcp_render_lib.lua +492 -0
  49. package/reaper/mcp_snapshot_lib.lua +547 -120
  50. package/reaper/mcp_snapshot_next.lua +10 -4
  51. package/reaper/mcp_snapshot_prev.lua +11 -4
  52. package/reaper/mcp_snapshot_quick_save.lua +7 -32
  53. package/reaper/mcp_snapshots_ui_lib.lua +258 -0
  54. package/reaper/mcp_tracks_lib.lua +409 -0
  55. package/sidecar/analysis.py +661 -0
  56. package/sidecar/chords.py +96 -0
  57. package/sidecar/mixcheck.py +295 -0
  58. package/sidecar/requirements-chords.txt +6 -0
  59. package/sidecar/requirements-separation.txt +3 -0
  60. package/sidecar/requirements.txt +17 -0
  61. package/sidecar/separation.py +142 -0
  62. package/sidecar/server.py +622 -0
  63. package/sidecar/tests/test_analysis.py +249 -0
  64. package/sidecar/tests/test_chords.py +117 -0
  65. package/sidecar/tests/test_mixcheck.py +163 -0
  66. package/sidecar/tests/test_separation.py +73 -0
  67. package/sidecar/tests/test_server.py +464 -0
  68. package/sidecar/tests/test_verify.py +243 -0
  69. package/sidecar/tests/test_voice_render.py +268 -0
  70. package/sidecar/tests/test_voice_sketch.py +140 -0
  71. package/sidecar/verify.py +224 -0
  72. package/sidecar/voice_render.py +295 -0
  73. package/sidecar/voice_sketch.py +280 -0
  74. package/claude-agents/critique.md +0 -147
  75. package/claude-agents/editor.md +0 -184
  76. package/claude-agents/levels.md +0 -107
  77. package/claude-agents/mastering.md +0 -129
  78. package/claude-agents/mixer.md +0 -248
  79. package/claude-agents/preflight.md +0 -149
  80. package/claude-agents/producer.md +0 -159
  81. package/claude-agents/setup.md +0 -150
  82. package/claude-agents/stems.md +0 -135
  83. package/claude-rules/architecture.md +0 -39
  84. package/claude-rules/development.md +0 -54
  85. package/claude-rules/lua-bridge.md +0 -50
  86. package/claude-rules/testing.md +0 -42
  87. package/claude-skills/learn-plugin.md +0 -123
  88. package/knowledge/CLAUDE.md +0 -71
  89. package/knowledge/plugins/_template.md +0 -82
  90. package/knowledge/plugins/fabfilter/pro-c-2.md +0 -117
  91. package/knowledge/plugins/fabfilter/pro-l-2.md +0 -95
  92. package/knowledge/plugins/fabfilter/pro-q-3.md +0 -112
  93. package/knowledge/plugins/neural-dsp/helix-native.md +0 -104
  94. package/knowledge/plugins/stock-reaper/js-1175-compressor.md +0 -94
  95. package/knowledge/plugins/stock-reaper/rea-comp.md +0 -100
  96. package/knowledge/plugins/stock-reaper/rea-delay.md +0 -95
  97. package/knowledge/plugins/stock-reaper/rea-eq.md +0 -103
  98. package/knowledge/plugins/stock-reaper/rea-gate.md +0 -99
  99. package/knowledge/plugins/stock-reaper/rea-limit.md +0 -75
  100. package/knowledge/plugins/stock-reaper/rea-verb.md +0 -76
  101. package/knowledge/reference/perceived-loudness.md +0 -122
  102. package/reaper/mcp_snapshot_manager.lua +0 -1001
  103. /package/{knowledge → claude/knowledge}/genres/_template.md +0 -0
  104. /package/{knowledge → claude/knowledge}/genres/electronic.md +0 -0
  105. /package/{knowledge → claude/knowledge}/genres/hip-hop.md +0 -0
  106. /package/{knowledge → claude/knowledge}/genres/metal.md +0 -0
  107. /package/{knowledge → claude/knowledge}/genres/orchestral.md +0 -0
  108. /package/{knowledge → claude/knowledge}/genres/pop.md +0 -0
  109. /package/{knowledge → claude/knowledge}/genres/rock.md +0 -0
  110. /package/{knowledge → claude/knowledge}/reference/compression.md +0 -0
  111. /package/{knowledge → claude/knowledge}/reference/frequencies.md +0 -0
  112. /package/{knowledge → claude/knowledge}/workflows/delivery.md +0 -0
  113. /package/{knowledge → claude/knowledge}/workflows/drum-bus.md +0 -0
  114. /package/{knowledge → claude/knowledge}/workflows/low-end.md +0 -0
  115. /package/{knowledge → claude/knowledge}/workflows/stem-prep.md +0 -0
  116. /package/{knowledge → claude/knowledge}/workflows/vocal-chain.md +0 -0
@@ -0,0 +1,324 @@
1
+ ---
2
+ name: new-extension
3
+ description: >
4
+ Builds or updates a reaper-mcp extension for one plugin or workflow, end to end:
5
+ checks it is feasible, picks the right kind (VST3 state editor, preset-file builder,
6
+ or bridge workflow), finds how the plugin stores its state, writes tools with
7
+ behaviour tests, proves them live on a scratch track (including that the sound
8
+ changes), documents them and teaches the mixing skills about it. Use when the user
9
+ wants Claude to control a plugin's internals that host parameters cannot reach
10
+ (wavetables, mod matrix, patch graphs, preset files), asks to "make an extension for
11
+ X", "add support for X", "build a plugin for reaper-mcp", or to change, extend or
12
+ fix an existing extension ("update the vital extension", "add macros to vital").
13
+ Triggers on "/new-extension".
14
+ argument-hint: '[plugin or workflow to build an extension for | update <extension>: <what to change>]'
15
+ license: MIT
16
+ metadata:
17
+ author: mthines
18
+ version: '2.0.0'
19
+ workflow_type: applied
20
+ tags:
21
+ - extensions
22
+ - plugins
23
+ - reaper
24
+ - vst3
25
+ - plugin-state
26
+ - preset-files
27
+ - synth
28
+ - tdd
29
+ - live-verification
30
+ ---
31
+
32
+ # /new-extension
33
+
34
+ This skill builds or updates an **opt-in extension** (`docs/EXTENSIONS.md`) for one plugin
35
+ or workflow: tools that reach what `set_fx_parameter` cannot, such as a synth's
36
+ wavetables, mod matrix and LFO shapes, or a guitar rig's preset files. Read
37
+ `docs/EXTENSIONS.md` first; it holds the manifest, the API, the three kinds of
38
+ extension and the trust model. Two worked examples ship in the repo, copy their shape,
39
+ not their details:
40
+
41
+ - `extensions/vital/`: a **state editor** (VST3 saved state, `api.fxState`).
42
+ - `extensions/helix/`: a **file-format builder** (the plugin's state is encrypted, so it
43
+ edits the documented preset file and imports it with a checked driver).
44
+
45
+ It works the way `/learn-plugin` does: do the research and the probing yourself, ask
46
+ the user only what you cannot find out, and prove the result on a scratch track
47
+ before calling it done. `/learn-plugin` teaches the mixing skills *about* a plugin;
48
+ this skill teaches reaper-mcp to *edit* one. They meet at the end (Step 8).
49
+
50
+ ## Trigger
51
+
52
+ The user types `/new-extension [plugin or workflow]`, `/new-extension update <extension>: <change>`,
53
+ or asks for control of a plugin's internals that the existing tools cannot give.
54
+
55
+ **Mode.** `ls extensions/` and `reaper-mcp extension list`. If the argument names an
56
+ existing extension, or the work is "add X to / fix Y in" one, you are in **update mode**
57
+ (see [Updating an extension](#updating-an-extension)); otherwise **new mode**: run Steps
58
+ 1-9.
59
+
60
+ ## Rules that hold the whole way
61
+
62
+ - **Scratch track only.** Never add the plugin to the user's tracks, never change their
63
+ tracks, FX, selection or transport, and never start playback of their material. The
64
+ user edits the session live, so track indices shift: look a track up by name before
65
+ each write batch. Name it `[MCP] extension-research <TAG>`, add it at the end of the
66
+ track list and delete it when you are done, **even when a step failed**.
67
+ - **Never write a user's file in place.** Anything that touches a preset or library file
68
+ writes a copy, or backs up first (`.bak`) and verifies afterwards. Do not run an import
69
+ or write against the user's real library slots; use a scratch slot.
70
+ - **Extensions never import core.** Only `@reaper-mcp/extension-api` (eslint enforces it).
71
+ - **Test behaviour, never implementation** (`CLAUDE.md`): drive the tools through a fake
72
+ `ExtensionApi` and real state fixtures; assert what the agent gets and what state
73
+ REAPER ends up with. Mock only REAPER.
74
+ - **Fixtures are small and impersonal.** No personal presets, no license keys, no file
75
+ paths from the user's machine.
76
+ - **Do not commit** unless the user asks. The tree may hold other sessions' edits: stage
77
+ explicit paths (or hunks) of your own files only.
78
+ - **Do not guess a format and write it.** A wrong write can make a plugin silently ignore
79
+ the change or load garbage. Stay on read-only probes until the parse is exact.
80
+
81
+ ## Instructions
82
+
83
+ ### Step 0: Load what earlier runs learned
84
+
85
+ Read `docs/EXTENSIONS.md`. Then read the LoreKit lessons for extensions (capped at 5:
86
+ `memory.list` in `project::reaper-mcp` with tag `loop::extension-lessons`, plus one
87
+ `memory.search` on the plugin's name) and the plugin's own record (`user::plugins::…`, or
88
+ `get_builtin_knowledge`). Treat them as considerations, not rules; verify against what
89
+ you see. If LoreKit is not connected, continue without it and say so in the report.
90
+
91
+ ### Step 1: Scope it, pick the kind, check it is worth building
92
+
93
+ From the arguments (or one question: "Which plugin or workflow, and what should Claude be
94
+ able to change in it?"), settle:
95
+
96
+ 1. **What for.** Read, edit, build, or all three. Smallest useful set wins: an extension
97
+ that reads and edits the thing people actually tweak beats one that models all of it.
98
+ 2. **Does it already exist?** `reaper-mcp extension list`, `ls extensions/`, and
99
+ `list_tool_categories`. If one covers it, switch to update mode.
100
+ 3. **Can the host parameters do it?** If `get_fx_parameters` already exposes everything
101
+ needed, stop: `/learn-plugin` is the right tool. An extension earns its place only
102
+ for state that lives outside the parameters.
103
+ 4. **Pick the kind** (the table in `docs/EXTENSIONS.md`). `search_fx` for the plugin first.
104
+
105
+ | Kind | Choose it when | Core of the work |
106
+ |---|---|---|
107
+ | **State editor** | A **VST3** build is installed and its saved state holds a recoverable format | `api.fxState` + `state.ts` parse/build (Steps 3-6 as written) |
108
+ | **File-format builder** | The state is closed or encrypted (probe shows no readable document) but the plugin imports a documented preset file | Parse/build/validate the file in Node; a checked import (`extensions/helix/src/import.ts`) |
109
+ | **Bridge composer** | No plugin internals needed, only a new multi-step REAPER workflow | `api.bridge` calls, `handlers.lua` only if a ReaScript call is missing from core |
110
+
111
+ `api.fxState` supports **VST3** only (VST2, AU, CLAP and FX inside containers are
112
+ refused). If only other formats are installed, say so and offer the file-format or
113
+ bridge route, or `/learn-plugin`.
114
+ 5. **Name it.** Lowercase, digits, dashes, 2-32 characters. It prefixes every tool
115
+ (`<name>_<tool>`).
116
+
117
+ ### Step 2: Research how the plugin stores its state
118
+
119
+ `WebSearch` / `WebFetch`, in this order: the vendor's docs on the **preset file format**,
120
+ open-source parsers or format write-ups, then forum threads. You are looking for:
121
+
122
+ - the preset format (JSON, XML, a binary chunk), its version field, and which parts are
123
+ safe to edit
124
+ - how the VST3 state wraps it (a JUCE bank, a raw preset, a custom blob)
125
+ - limits that make an edit invalid (counts, ranges, name rules)
126
+ - the license, if you will port any parser logic (credit it, or write your own)
127
+
128
+ Keep the URLs. Do not present guesses as facts. If the format is closed and
129
+ undocumented, say so now: you will have to work it out from probes in Step 3, and
130
+ should tell the user it may not be feasible.
131
+
132
+ ### Step 3: Scaffold, then probe the real state
133
+
134
+ 1. `reaper-mcp extension new <name> --dir extensions`. It scaffolds plain JavaScript
135
+ (`index.mjs`, a manifest). In this repo make it a TypeScript project like
136
+ `extensions/vital/`: copy its `project.json` (tag `type:extension`, esbuild to
137
+ `dist/index.mjs`), `tsconfig.json`, `package.json` and `vitest.config.ts`, set `main`
138
+ to `dist/index.mjs`, delete the scaffold's `index.mjs`, and run `pnpm install` once so
139
+ the new package is linked (the `extensions/*` workspace glob covers it).
140
+ 2. Add the plugin to the scratch track (`add_fx`, exact FX name from `search_fx`).
141
+ Check `get_fx_parameters`: a plugin showing only 3 parameters is not loaded (offline),
142
+ toggle it online first.
143
+ 3. **State editor:** write a throwaway **probe tool** that uses `api.fxState.get` and
144
+ returns, as JSON: state length, the 44-byte header's two length fields, the first and
145
+ last 64 bytes (hex and ASCII), every printable-string run over 8 characters, and
146
+ whether a JSON or XML document starts inside it and where it ends.
147
+ **File-format builder:** if the probe shows an encrypted or opaque blob, record that
148
+ (it is the reason for this kind) and work from the preset files instead: collect two or
149
+ three real files from the user's library or the vendor's factory content, read them,
150
+ and write the layout down.
151
+ 4. Build it (`pnpm nx build <name>`), link it (`reaper-mcp extension add <name>`, a
152
+ symlink to this clone's `extensions/<name>`), and run it with the harness. A running
153
+ Claude Code session only sees new tools after a restart, so use:
154
+
155
+ ```bash
156
+ node scripts/ext-call.mjs <name>_probe '{"trackIndex": N}'
157
+ ```
158
+
159
+ (Needs `pnpm nx build reaper-mcp-server` and a running bridge.)
160
+ 5. Learn the encoding by **changing one thing and diffing**: set one parameter with
161
+ `set_fx_parameter`, run the probe again, compare the bytes. Then change one thing
162
+ the host cannot reach (load a preset, switch a wavetable) and diff again. Save two or
163
+ three real states as **test fixtures**.
164
+ 6. Write down the layout: where the editable document starts and ends, which length
165
+ fields wrap it (and what they count), and anything after it that must be preserved.
166
+
167
+ Stay on probes until the parse is exact on every fixture. Delete the probe tool before
168
+ Step 7.
169
+
170
+ ### Step 4: Design the tools
171
+
172
+ Propose the tool list to the user in one short message (name, one line each, what is
173
+ read and what is written) and fold in their answer. Defaults that have worked:
174
+
175
+ - `read_*`: a **summary first** (structure, names, counts), raw values only on request
176
+ (`settingsMatching`-style filter, pagination). Never return the whole state.
177
+ - `write_*`: validate everything first and write nothing on one bad edit; `dryRun: true`
178
+ returns what would change with before → after; one undo point; say how to undo
179
+ (`undo_project`).
180
+ - One **domain tool** for the thing people want to build (Vital: `set_wavetable`), not a
181
+ generic "set any byte".
182
+ - `z.coerce.number()` for numbers, descriptions that tell an agent *when* to use the tool
183
+ and what the units are. Locate the plugin automatically when exactly one is on the track.
184
+ - Keep the user's selection, transport and track list as they were.
185
+
186
+ ### Step 5: Implement, with behaviour tests first
187
+
188
+ Split the extension like `extensions/vital/src/`:
189
+
190
+ - `state.ts` (or `hlx.ts`-style for a file format): `parse` and `build`, pure and
191
+ byte-exact. `build` must keep every byte it does not edit, fix every inner length field
192
+ the edit changes, and (state editor) leave the VST3 wrapper's two outer lengths to
193
+ `api.fxState.set`.
194
+ - `patch.ts` (or similar): the edits as pure functions over the parsed document, each
195
+ returning an error list instead of throwing.
196
+ - `index.ts`: tool registration and the read → mutate → write helper.
197
+ - `__fixtures__/fake-api.ts`: a fake `ExtensionApi` (copy Vital's).
198
+
199
+ Tests (`*.test.ts`), each named after a behaviour:
200
+
201
+ - **round trip**: `build(parse(x), {})` returns `x` byte for byte, on every real fixture
202
+ - an edit changes exactly what was asked and nothing else
203
+ - a bad edit is refused and `set` is never called
204
+ - a wrong FX on the track gives a clear message, not a write
205
+ - a state the plugin changed after load (extra trailing bytes) still edits correctly
206
+ - file builders: an invalid file is refused before anything is written; an overwrite keeps
207
+ a `.bak`
208
+
209
+ Run `pnpm nx run-many --target=test,lint,typecheck,build --projects=<name>` until green.
210
+
211
+ ### Step 6: Prove it live
212
+
213
+ On the scratch track, with the harness (a fresh process, so no restart):
214
+
215
+ 1. `read_*` and compare with what you know about the loaded patch.
216
+ 2. `write_*` with `dryRun: true`, then for real. Read it back: the change must be there.
217
+ 3. **Prove it sounds different.** Add a MIDI item with a note or chord on the scratch
218
+ track (`create_midi_item`, `insert_midi_notes`), then `analyze_track_offline` before
219
+ and after the edit. Spectral centroid, band balance or loudness must move the way the
220
+ edit should move them. A read-back that matches does not prove the plugin loaded it.
221
+ 4. Edit **twice in a row** (the state the plugin saves after loading your first write can
222
+ differ from the one you wrote; the second write is where a stale length shows up).
223
+ 5. `undo_project` once and read back: the edit is gone.
224
+ 6. Call a tool with a deliberately bad input: the refusal must be readable and change nothing.
225
+
226
+ If anything fails, keep the failing state's bytes (`api.fxState.get`) as a new fixture and
227
+ fix it with a test first. If it cannot be made reliable, say so in the report and do not
228
+ ship a tool that only sometimes works.
229
+
230
+ ### Step 7: Gates
231
+
232
+ `reaper-mcp doctor` must list the extension as ok (manifest valid, `main` built, `lua`
233
+ present). Then everything: `pnpm nx run-many --target=build,lint,typecheck,test`, run with
234
+ `REAPER_RESOURCE_PATH` pointed at a temp folder so live-REAPER suites don't touch the
235
+ user's bridge. Delete the scratch track.
236
+
237
+ ### Step 8: Teach the rest of the system
238
+
239
+ 1. **Docs.** All four, in the same change:
240
+ - `docs/EXTENSIONS.md`: a section for the extension (what it does, a tool table, what is
241
+ not covered, env vars) next to Vital and Helix, plus a row in the table at the top.
242
+ - **Root `README.md`**: a row in the "Extensions" table (`## 🧩 Extensions`) with the emoji,
243
+ name, and what Claude can do with it in plain words for a non-coder, and the plugin in
244
+ the FAQ answer if it names them. Plain words, no jargon.
245
+ - `docs/assets/extensions.svg`: if it is a headline extension, add or swap a card so the
246
+ picture stays true (keep the file valid: `xmllint --noout`).
247
+ - `CLAUDE.md` (the structure block) and `docs/REFERENCE.md` (Project Structure).
248
+
249
+ Rebuild with `--skip-nx-cache` after editing knowledge or `.claude` files (Nx ignores
250
+ asset-dir changes).
251
+ 2. **Plugin record.** Write or update the plugin's `user::plugins::<vendor>/<plugin>`
252
+ record in LoreKit (`project::reaper-mcp`) following
253
+ `knowledge/reference/memory-protocol.md`: copy the built-in record's tags, add
254
+ `extension::<name>`, and put the workflow, the raw-name vs host-name difference, what
255
+ must be enabled before it sounds, and what you verified live in `## Learned notes`. A
256
+ write replaces the record: read, merge, write.
257
+ 3. **Lessons.** Save each non-obvious gotcha you hit as a LoreKit lesson in
258
+ `project::reaper-mcp`, tag `loop::extension-lessons`, phrased as an observation with
259
+ what you saw. Do not save anything that is already in this file or in the code.
260
+
261
+ ### Step 9: Report
262
+
263
+ One short message: what the extension does and its tools, what you verified live (with
264
+ the numbers from Step 6), what is **not** covered or not verified, and the install line
265
+ (`reaper-mcp extension add <name>`, then restart the MCP server). Offer to commit and
266
+ open a PR; do not do either unprompted.
267
+
268
+ ## Updating an extension
269
+
270
+ For "add X to", "fix Y in" or "support Z in" an existing extension. Same rules, shorter loop:
271
+
272
+ 1. **Read it first.** Its manifest, `README` section in `docs/EXTENSIONS.md`, tool list,
273
+ tests and fixtures; then `git log --oneline -- extensions/<name>` for how it grew. Read
274
+ the Step 0 lessons.
275
+ 2. **Reproduce or probe.** A bug: capture the failing state (`api.fxState.get` on a scratch
276
+ track) as a fixture and write the failing test first. A new capability: probe the one
277
+ thing it needs (Step 3, steps 2-6) on the scratch track, diffing as before.
278
+ 3. **Keep tools compatible.** Existing tool names and parameters keep working; add
279
+ optional parameters rather than renaming. A breaking change needs the user's yes and a
280
+ `version` major bump.
281
+ 4. **Behaviour tests, then code,** then Steps 6 and 7 for the changed tools (live proof of
282
+ the new behaviour **and** one regression check on an old tool).
283
+ 5. **Bump `version`** in the manifest (patch for fixes, minor for new tools or parameters;
284
+ `apiVersion` only changes when `libs/extension-api` does).
285
+ 6. **Docs.** Update the extension's section in `docs/EXTENSIONS.md` (move the item out of
286
+ its "Not yet" list), the root README row if what Claude can do changed, and the plugin's
287
+ LoreKit record. Lessons only for new gotchas.
288
+ 7. Report as in Step 9, adding what changed for existing users (usually: rebuild, restart).
289
+
290
+ ## Definition of done
291
+
292
+ - [ ] Parse round-trips every fixture byte for byte (or the file builder validates every fixture)
293
+ - [ ] Tools validate before writing; `dryRun` on every write; one undo point
294
+ - [ ] Live proof on a scratch track: read, write, read back, audible change, two writes, undo
295
+ - [ ] `reaper-mcp doctor` shows the extension ok; build, lint, typecheck, test green
296
+ - [ ] `docs/EXTENSIONS.md`, root `README.md`, `CLAUDE.md`, `docs/REFERENCE.md` updated
297
+ - [ ] LoreKit plugin record and lessons written (or the report says why not)
298
+ - [ ] Scratch track deleted; the user's tracks, selection and transport untouched
299
+ - [ ] Nothing committed unless asked
300
+
301
+ ## Known gotchas (seed lessons, from building `vital` and `helix`)
302
+
303
+ - REAPER ignores changed VST3 state unless the FX gets a **new `FXID`**, and it checks two
304
+ outer length fields (header bytes 32..35 = state length; state bytes 0..3 = state length
305
+ minus 16). `api.fxState.set` handles both; never write the chunk by hand.
306
+ - After loading an edited state, a plugin can save itself with a few **extra trailing
307
+ bytes** while REAPER keeps the lengths it was given. Parse tolerantly, and keep trailing
308
+ bytes on `build`.
309
+ - A plugin whose host exposes only 3 parameters is **offline** (not loaded). Toggle it
310
+ online (`set_fx_offline`) before probing or writing, and refuse to write while offline.
311
+ - Some plugins (Helix Native) keep an **encrypted** state blob: do not try to edit it. Use
312
+ the plugin's preset file and verify the result on disk, rolling back if more than the
313
+ intended slot changed.
314
+ - The bridge's JSON decode is flat (SWS has no `CF_Json_Parse`): extension params travel as
315
+ a JSON string (`paramsJson`, decoded for you before your Lua handler runs), and large
316
+ state travels as files in the bridge folder, never inside a command.
317
+ - `mcp_bridge.lua` is near Lua's 200-locals limit. New Lua goes in a lib file or on the
318
+ `ext` table, not as new top-level locals.
319
+ - A write reloads the plugin, so held notes cut off. Do not write mid-playback.
320
+ - `register(api)` must finish within 10 s: register tools, do slow work inside handlers.
321
+ - Extension tools are not in the Claude Code allow-list, so each new one asks for
322
+ permission the first time.
323
+ - A running Claude Code session only sees a new or changed extension after a restart. Use
324
+ `node scripts/ext-call.mjs` to test in the meantime.