mxl-agent 0.1.0__tar.gz

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 (363) hide show
  1. mxl_agent-0.1.0/.claude/skills/mxl-agent/SKILL.md +309 -0
  2. mxl_agent-0.1.0/.claude/skills/mxl-agent/evals/evals.json +168 -0
  3. mxl_agent-0.1.0/.claude/skills/mxl-agent/evals/fixtures/chord_symbols.musicxml +70 -0
  4. mxl_agent-0.1.0/.claude/skills/mxl-agent/evals/fixtures/divisions_change.musicxml +53 -0
  5. mxl_agent-0.1.0/.claude/skills/mxl-agent/references/command-catalog.md +900 -0
  6. mxl_agent-0.1.0/.claude/skills/mxl-agent/references/musical-semantics.md +40 -0
  7. mxl_agent-0.1.0/.claude/skills/mxl-agent/references/preservation-and-safety.md +29 -0
  8. mxl_agent-0.1.0/.claude/skills/mxl-agent/references/selectors.md +565 -0
  9. mxl_agent-0.1.0/.claude/skills/mxl-agent/scripts/mxl-agent.sh +34 -0
  10. mxl_agent-0.1.0/.claude-plugin/plugin.json +10 -0
  11. mxl_agent-0.1.0/.gitattributes +7 -0
  12. mxl_agent-0.1.0/.github/workflows/ci.yml +14 -0
  13. mxl_agent-0.1.0/.github/workflows/release.yml +40 -0
  14. mxl_agent-0.1.0/.gitignore +16 -0
  15. mxl_agent-0.1.0/.mcp.json +18 -0
  16. mxl_agent-0.1.0/.python-version +1 -0
  17. mxl_agent-0.1.0/CHANGELOG.md +58 -0
  18. mxl_agent-0.1.0/CLAUDE.md +43 -0
  19. mxl_agent-0.1.0/COMPATIBILITY.md +44 -0
  20. mxl_agent-0.1.0/LICENSE +21 -0
  21. mxl_agent-0.1.0/NOTICE +84 -0
  22. mxl_agent-0.1.0/PKG-INFO +80 -0
  23. mxl_agent-0.1.0/README.md +63 -0
  24. mxl_agent-0.1.0/ROADMAP.md +2881 -0
  25. mxl_agent-0.1.0/hooks/block-workspace-mutation.sh +49 -0
  26. mxl_agent-0.1.0/hooks/hooks.json +15 -0
  27. mxl_agent-0.1.0/mxl_agent-skill.md +2088 -0
  28. mxl_agent-0.1.0/pyproject.toml +95 -0
  29. mxl_agent-0.1.0/schemas/musicxml-3.1/container.xsd +188 -0
  30. mxl_agent-0.1.0/schemas/musicxml-3.1/musicxml.xsd +6081 -0
  31. mxl_agent-0.1.0/schemas/musicxml-3.1/xlink.xsd +57 -0
  32. mxl_agent-0.1.0/schemas/musicxml-3.1/xml.xsd +145 -0
  33. mxl_agent-0.1.0/schemas/musicxml-4.0/container.xsd +188 -0
  34. mxl_agent-0.1.0/schemas/musicxml-4.0/musicxml.xsd +11028 -0
  35. mxl_agent-0.1.0/schemas/musicxml-4.0/xlink.xsd +118 -0
  36. mxl_agent-0.1.0/schemas/musicxml-4.0/xml.xsd +140 -0
  37. mxl_agent-0.1.0/scripts/check.py +273 -0
  38. mxl_agent-0.1.0/src/mxl_agent/__init__.py +1 -0
  39. mxl_agent-0.1.0/src/mxl_agent/adapters/__init__.py +5 -0
  40. mxl_agent-0.1.0/src/mxl_agent/adapters/chord_text.py +89 -0
  41. mxl_agent-0.1.0/src/mxl_agent/adapters/jjazzlab_sng.py +200 -0
  42. mxl_agent-0.1.0/src/mxl_agent/adapters/mcp.py +246 -0
  43. mxl_agent-0.1.0/src/mxl_agent/adapters/mei.py +59 -0
  44. mxl_agent-0.1.0/src/mxl_agent/adapters/musescore.py +206 -0
  45. mxl_agent-0.1.0/src/mxl_agent/adapters/render_identity.py +237 -0
  46. mxl_agent-0.1.0/src/mxl_agent/adapters/timemap.py +77 -0
  47. mxl_agent-0.1.0/src/mxl_agent/adapters/vendor/ext_apps_client.js +86 -0
  48. mxl_agent-0.1.0/src/mxl_agent/adapters/verovio.py +154 -0
  49. mxl_agent-0.1.0/src/mxl_agent/analysis/__init__.py +49 -0
  50. mxl_agent-0.1.0/src/mxl_agent/analysis/_cross_part.py +71 -0
  51. mxl_agent-0.1.0/src/mxl_agent/analysis/_harmony.py +141 -0
  52. mxl_agent-0.1.0/src/mxl_agent/analysis/_key.py +58 -0
  53. mxl_agent-0.1.0/src/mxl_agent/analysis/_time_signature.py +50 -0
  54. mxl_agent-0.1.0/src/mxl_agent/analysis/_transposition.py +28 -0
  55. mxl_agent-0.1.0/src/mxl_agent/analysis/active_key.py +74 -0
  56. mxl_agent-0.1.0/src/mxl_agent/analysis/base.py +30 -0
  57. mxl_agent-0.1.0/src/mxl_agent/analysis/cadence_candidate.py +84 -0
  58. mxl_agent-0.1.0/src/mxl_agent/analysis/chord_recognition.py +101 -0
  59. mxl_agent-0.1.0/src/mxl_agent/analysis/chord_tone.py +168 -0
  60. mxl_agent-0.1.0/src/mxl_agent/analysis/doubling.py +108 -0
  61. mxl_agent-0.1.0/src/mxl_agent/analysis/harmonic_rhythm.py +105 -0
  62. mxl_agent-0.1.0/src/mxl_agent/analysis/interval_above_bass.py +137 -0
  63. mxl_agent-0.1.0/src/mxl_agent/analysis/lint.py +249 -0
  64. mxl_agent-0.1.0/src/mxl_agent/analysis/local_key.py +131 -0
  65. mxl_agent-0.1.0/src/mxl_agent/analysis/non_chord_tone.py +196 -0
  66. mxl_agent-0.1.0/src/mxl_agent/analysis/note_density.py +61 -0
  67. mxl_agent-0.1.0/src/mxl_agent/analysis/parallel_motion.py +132 -0
  68. mxl_agent-0.1.0/src/mxl_agent/analysis/playability.py +119 -0
  69. mxl_agent-0.1.0/src/mxl_agent/analysis/polyphony.py +53 -0
  70. mxl_agent-0.1.0/src/mxl_agent/analysis/registry.py +31 -0
  71. mxl_agent-0.1.0/src/mxl_agent/analysis/roman_numeral.py +145 -0
  72. mxl_agent-0.1.0/src/mxl_agent/analysis/run.py +22 -0
  73. mxl_agent-0.1.0/src/mxl_agent/analysis/scale_degree.py +175 -0
  74. mxl_agent-0.1.0/src/mxl_agent/analysis/syncopation.py +104 -0
  75. mxl_agent-0.1.0/src/mxl_agent/analysis/voice_leading.py +113 -0
  76. mxl_agent-0.1.0/src/mxl_agent/cli.py +1471 -0
  77. mxl_agent-0.1.0/src/mxl_agent/document/mutation_guard.py +80 -0
  78. mxl_agent-0.1.0/src/mxl_agent/document/score_document.py +83 -0
  79. mxl_agent-0.1.0/src/mxl_agent/document/source_locator.py +45 -0
  80. mxl_agent-0.1.0/src/mxl_agent/errors.py +29 -0
  81. mxl_agent-0.1.0/src/mxl_agent/generate/__init__.py +5 -0
  82. mxl_agent-0.1.0/src/mxl_agent/generate/accompaniment.py +236 -0
  83. mxl_agent-0.1.0/src/mxl_agent/generate/arrangement.py +103 -0
  84. mxl_agent-0.1.0/src/mxl_agent/generate/from_midi.py +392 -0
  85. mxl_agent-0.1.0/src/mxl_agent/generate/lead_sheet.py +300 -0
  86. mxl_agent-0.1.0/src/mxl_agent/generate/reharmonization.py +305 -0
  87. mxl_agent-0.1.0/src/mxl_agent/generate/voicing.py +258 -0
  88. mxl_agent-0.1.0/src/mxl_agent/midi/__init__.py +2 -0
  89. mxl_agent-0.1.0/src/mxl_agent/midi/reader.py +232 -0
  90. mxl_agent-0.1.0/src/mxl_agent/model/addresses.py +17 -0
  91. mxl_agent-0.1.0/src/mxl_agent/model/chord_recognition.py +91 -0
  92. mxl_agent-0.1.0/src/mxl_agent/model/chord_tones.py +145 -0
  93. mxl_agent-0.1.0/src/mxl_agent/model/diff.py +34 -0
  94. mxl_agent-0.1.0/src/mxl_agent/model/duration.py +169 -0
  95. mxl_agent-0.1.0/src/mxl_agent/model/events.py +38 -0
  96. mxl_agent-0.1.0/src/mxl_agent/model/harmony.py +561 -0
  97. mxl_agent-0.1.0/src/mxl_agent/model/instrument_ranges.py +81 -0
  98. mxl_agent-0.1.0/src/mxl_agent/model/key_signature.py +135 -0
  99. mxl_agent-0.1.0/src/mxl_agent/model/layout.py +61 -0
  100. mxl_agent-0.1.0/src/mxl_agent/model/merge.py +135 -0
  101. mxl_agent-0.1.0/src/mxl_agent/model/metadata.py +36 -0
  102. mxl_agent-0.1.0/src/mxl_agent/model/non_chord_tone.py +54 -0
  103. mxl_agent-0.1.0/src/mxl_agent/model/pitch.py +177 -0
  104. mxl_agent-0.1.0/src/mxl_agent/model/roman_numeral.py +300 -0
  105. mxl_agent-0.1.0/src/mxl_agent/model/score_index.py +208 -0
  106. mxl_agent-0.1.0/src/mxl_agent/model/selection.py +203 -0
  107. mxl_agent-0.1.0/src/mxl_agent/model/semantic_diff.py +309 -0
  108. mxl_agent-0.1.0/src/mxl_agent/model/timeline.py +299 -0
  109. mxl_agent-0.1.0/src/mxl_agent/model/timewise.py +126 -0
  110. mxl_agent-0.1.0/src/mxl_agent/operations/__init__.py +44 -0
  111. mxl_agent-0.1.0/src/mxl_agent/operations/_key.py +71 -0
  112. mxl_agent-0.1.0/src/mxl_agent/operations/_measure_attributes.py +84 -0
  113. mxl_agent-0.1.0/src/mxl_agent/operations/_note_selection.py +120 -0
  114. mxl_agent-0.1.0/src/mxl_agent/operations/_note_xml.py +117 -0
  115. mxl_agent-0.1.0/src/mxl_agent/operations/_offset_position.py +65 -0
  116. mxl_agent-0.1.0/src/mxl_agent/operations/_parts.py +44 -0
  117. mxl_agent-0.1.0/src/mxl_agent/operations/_score_header.py +39 -0
  118. mxl_agent-0.1.0/src/mxl_agent/operations/_xml_order.py +26 -0
  119. mxl_agent-0.1.0/src/mxl_agent/operations/barlines.py +247 -0
  120. mxl_agent-0.1.0/src/mxl_agent/operations/base.py +31 -0
  121. mxl_agent-0.1.0/src/mxl_agent/operations/change_duration.py +234 -0
  122. mxl_agent-0.1.0/src/mxl_agent/operations/chord_symbol_style.py +157 -0
  123. mxl_agent-0.1.0/src/mxl_agent/operations/copy_notes.py +330 -0
  124. mxl_agent-0.1.0/src/mxl_agent/operations/create_tuplet.py +343 -0
  125. mxl_agent-0.1.0/src/mxl_agent/operations/credit.py +186 -0
  126. mxl_agent-0.1.0/src/mxl_agent/operations/delete_note.py +172 -0
  127. mxl_agent-0.1.0/src/mxl_agent/operations/direction_spanners.py +586 -0
  128. mxl_agent-0.1.0/src/mxl_agent/operations/directions.py +393 -0
  129. mxl_agent-0.1.0/src/mxl_agent/operations/envelope.py +67 -0
  130. mxl_agent-0.1.0/src/mxl_agent/operations/harmony.py +339 -0
  131. mxl_agent-0.1.0/src/mxl_agent/operations/insert_note.py +328 -0
  132. mxl_agent-0.1.0/src/mxl_agent/operations/layout.py +321 -0
  133. mxl_agent-0.1.0/src/mxl_agent/operations/lyrics.py +248 -0
  134. mxl_agent-0.1.0/src/mxl_agent/operations/measure_operations.py +448 -0
  135. mxl_agent-0.1.0/src/mxl_agent/operations/merge_tied_notes.py +228 -0
  136. mxl_agent-0.1.0/src/mxl_agent/operations/metadata.py +299 -0
  137. mxl_agent-0.1.0/src/mxl_agent/operations/move_note.py +313 -0
  138. mxl_agent-0.1.0/src/mxl_agent/operations/noop.py +42 -0
  139. mxl_agent-0.1.0/src/mxl_agent/operations/notations.py +422 -0
  140. mxl_agent-0.1.0/src/mxl_agent/operations/notes.py +171 -0
  141. mxl_agent-0.1.0/src/mxl_agent/operations/registry.py +28 -0
  142. mxl_agent-0.1.0/src/mxl_agent/operations/rename_part.py +186 -0
  143. mxl_agent-0.1.0/src/mxl_agent/operations/repair.py +326 -0
  144. mxl_agent-0.1.0/src/mxl_agent/operations/respell.py +167 -0
  145. mxl_agent-0.1.0/src/mxl_agent/operations/score_defaults.py +286 -0
  146. mxl_agent-0.1.0/src/mxl_agent/operations/set_attributes.py +422 -0
  147. mxl_agent-0.1.0/src/mxl_agent/operations/spanners.py +383 -0
  148. mxl_agent-0.1.0/src/mxl_agent/operations/split_note.py +236 -0
  149. mxl_agent-0.1.0/src/mxl_agent/operations/transpose.py +205 -0
  150. mxl_agent-0.1.0/src/mxl_agent/package/__init__.py +0 -0
  151. mxl_agent-0.1.0/src/mxl_agent/package/compare.py +96 -0
  152. mxl_agent-0.1.0/src/mxl_agent/package/manifest.py +33 -0
  153. mxl_agent-0.1.0/src/mxl_agent/package/reader.py +189 -0
  154. mxl_agent-0.1.0/src/mxl_agent/package/safety.py +262 -0
  155. mxl_agent-0.1.0/src/mxl_agent/package/writer.py +96 -0
  156. mxl_agent-0.1.0/src/mxl_agent/patch/__init__.py +0 -0
  157. mxl_agent-0.1.0/src/mxl_agent/patch/apply.py +51 -0
  158. mxl_agent-0.1.0/src/mxl_agent/patch/inverse.py +88 -0
  159. mxl_agent-0.1.0/src/mxl_agent/patch/model.py +136 -0
  160. mxl_agent-0.1.0/src/mxl_agent/patch/plan.py +79 -0
  161. mxl_agent-0.1.0/src/mxl_agent/patch/repair.py +44 -0
  162. mxl_agent-0.1.0/src/mxl_agent/query/inspect.py +134 -0
  163. mxl_agent-0.1.0/src/mxl_agent/query/pagination.py +90 -0
  164. mxl_agent-0.1.0/src/mxl_agent/query/projection.py +101 -0
  165. mxl_agent-0.1.0/src/mxl_agent/result.py +40 -0
  166. mxl_agent-0.1.0/src/mxl_agent/service/__init__.py +13 -0
  167. mxl_agent-0.1.0/src/mxl_agent/service/session.py +96 -0
  168. mxl_agent-0.1.0/src/mxl_agent/session/__init__.py +0 -0
  169. mxl_agent-0.1.0/src/mxl_agent/session/checkpoints.py +63 -0
  170. mxl_agent-0.1.0/src/mxl_agent/session/revisions.py +92 -0
  171. mxl_agent-0.1.0/src/mxl_agent/session/selection_store.py +47 -0
  172. mxl_agent-0.1.0/src/mxl_agent/session/transaction.py +451 -0
  173. mxl_agent-0.1.0/src/mxl_agent/session/workspace.py +272 -0
  174. mxl_agent-0.1.0/src/mxl_agent/validation/__init__.py +0 -0
  175. mxl_agent-0.1.0/src/mxl_agent/validation/compatibility.py +65 -0
  176. mxl_agent-0.1.0/src/mxl_agent/validation/issue.py +15 -0
  177. mxl_agent-0.1.0/src/mxl_agent/validation/package.py +29 -0
  178. mxl_agent-0.1.0/src/mxl_agent/validation/preservation.py +80 -0
  179. mxl_agent-0.1.0/src/mxl_agent/validation/relationships.py +113 -0
  180. mxl_agent-0.1.0/src/mxl_agent/validation/reopen.py +34 -0
  181. mxl_agent-0.1.0/src/mxl_agent/validation/report.py +70 -0
  182. mxl_agent-0.1.0/src/mxl_agent/validation/schema.py +29 -0
  183. mxl_agent-0.1.0/src/mxl_agent/validation/temporal.py +63 -0
  184. mxl_agent-0.1.0/src/mxl_agent/viewer/__init__.py +10 -0
  185. mxl_agent-0.1.0/src/mxl_agent/viewer/server.py +633 -0
  186. mxl_agent-0.1.0/src/mxl_agent/viewer/static/index.html +210 -0
  187. mxl_agent-0.1.0/src/mxl_agent/viewer/static/viewer.js +790 -0
  188. mxl_agent-0.1.0/src/mxl_agent/xml/__init__.py +0 -0
  189. mxl_agent-0.1.0/src/mxl_agent/xml/canonical.py +41 -0
  190. mxl_agent-0.1.0/src/mxl_agent/xml/detect.py +35 -0
  191. mxl_agent-0.1.0/src/mxl_agent/xml/parser.py +84 -0
  192. mxl_agent-0.1.0/src/mxl_agent/xml/schema.py +140 -0
  193. mxl_agent-0.1.0/tests/__init__.py +0 -0
  194. mxl_agent-0.1.0/tests/corpus/test_fixture_sweep.py +83 -0
  195. mxl_agent-0.1.0/tests/fixtures/__init__.py +0 -0
  196. mxl_agent-0.1.0/tests/fixtures/builder.py +138 -0
  197. mxl_agent-0.1.0/tests/fixtures/corpus/musescore/annotated_score.musicxml +198 -0
  198. mxl_agent-0.1.0/tests/fixtures/corpus/musescore/chord_symbols.musicxml +160 -0
  199. mxl_agent-0.1.0/tests/fixtures/corpus/musescore/piano_two_staves.musicxml +155 -0
  200. mxl_agent-0.1.0/tests/fixtures/expected/summary_annotated_score.json +13 -0
  201. mxl_agent-0.1.0/tests/fixtures/expected/summary_single_note.json +10 -0
  202. mxl_agent-0.1.0/tests/fixtures/minimal/annotated_score.musicxml +88 -0
  203. mxl_agent-0.1.0/tests/fixtures/minimal/bb_trumpet.musicxml +38 -0
  204. mxl_agent-0.1.0/tests/fixtures/minimal/bb_trumpet_dorian.musicxml +39 -0
  205. mxl_agent-0.1.0/tests/fixtures/minimal/cadence_progression.musicxml +130 -0
  206. mxl_agent-0.1.0/tests/fixtures/minimal/chord_symbols.musicxml +70 -0
  207. mxl_agent-0.1.0/tests/fixtures/minimal/chord_tone_notes.musicxml +87 -0
  208. mxl_agent-0.1.0/tests/fixtures/minimal/chord_with_frame_and_inversion.musicxml +63 -0
  209. mxl_agent-0.1.0/tests/fixtures/minimal/dangling_tie.musicxml +39 -0
  210. mxl_agent-0.1.0/tests/fixtures/minimal/divisions_change.musicxml +53 -0
  211. mxl_agent-0.1.0/tests/fixtures/minimal/duplicate_and_nonnumeric_measure_numbers.musicxml +50 -0
  212. mxl_agent-0.1.0/tests/fixtures/minimal/flat_key_chromatic_note.musicxml +35 -0
  213. mxl_agent-0.1.0/tests/fixtures/minimal/invalid_missing_part_list.musicxml +15 -0
  214. mxl_agent-0.1.0/tests/fixtures/minimal/jjazzlab_mixed_meter.sng +58 -0
  215. mxl_agent-0.1.0/tests/fixtures/minimal/jjazzlab_simple.sng +48 -0
  216. mxl_agent-0.1.0/tests/fixtures/minimal/key_change.musicxml +55 -0
  217. mxl_agent-0.1.0/tests/fixtures/minimal/non_chord_tone_notes.musicxml +124 -0
  218. mxl_agent-0.1.0/tests/fixtures/minimal/partial_measure.musicxml +35 -0
  219. mxl_agent-0.1.0/tests/fixtures/minimal/piano_two_staves.musicxml +65 -0
  220. mxl_agent-0.1.0/tests/fixtures/minimal/quarter_tone_note.musicxml +36 -0
  221. mxl_agent-0.1.0/tests/fixtures/minimal/scale_degree_notes.musicxml +74 -0
  222. mxl_agent-0.1.0/tests/fixtures/minimal/short_measure.musicxml +35 -0
  223. mxl_agent-0.1.0/tests/fixtures/minimal/single_note.musicxml +34 -0
  224. mxl_agent-0.1.0/tests/fixtures/minimal/syncopation_notes.musicxml +57 -0
  225. mxl_agent-0.1.0/tests/fixtures/minimal/transposition_change.musicxml +54 -0
  226. mxl_agent-0.1.0/tests/fixtures/minimal/two_voices_one_staff.musicxml +62 -0
  227. mxl_agent-0.1.0/tests/fixtures/raw_zip.py +121 -0
  228. mxl_agent-0.1.0/tests/golden/__init__.py +0 -0
  229. mxl_agent-0.1.0/tests/golden/test_summary_golden.py +57 -0
  230. mxl_agent-0.1.0/tests/integration/__init__.py +0 -0
  231. mxl_agent-0.1.0/tests/integration/test_mcp_server.py +146 -0
  232. mxl_agent-0.1.0/tests/integration/test_mei_verovio.py +93 -0
  233. mxl_agent-0.1.0/tests/integration/test_musescore_adapter.py +175 -0
  234. mxl_agent-0.1.0/tests/integration/test_mvp_acceptance.py +246 -0
  235. mxl_agent-0.1.0/tests/integration/test_render_identity_verovio.py +81 -0
  236. mxl_agent-0.1.0/tests/integration/test_render_verovio.py +238 -0
  237. mxl_agent-0.1.0/tests/integration/test_viewer_server_verovio.py +309 -0
  238. mxl_agent-0.1.0/tests/property/__init__.py +0 -0
  239. mxl_agent-0.1.0/tests/property/test_generative_pipeline.py +198 -0
  240. mxl_agent-0.1.0/tests/security/__init__.py +0 -0
  241. mxl_agent-0.1.0/tests/security/test_midi_safety.py +80 -0
  242. mxl_agent-0.1.0/tests/security/test_mxl_safety.py +78 -0
  243. mxl_agent-0.1.0/tests/security/test_schema_safety.py +58 -0
  244. mxl_agent-0.1.0/tests/security/test_xml_safety.py +102 -0
  245. mxl_agent-0.1.0/tests/security/test_zip_safety.py +168 -0
  246. mxl_agent-0.1.0/tests/unit/__init__.py +0 -0
  247. mxl_agent-0.1.0/tests/unit/test_adapter_chord_text.py +81 -0
  248. mxl_agent-0.1.0/tests/unit/test_adapter_mcp.py +57 -0
  249. mxl_agent-0.1.0/tests/unit/test_adapter_mei.py +26 -0
  250. mxl_agent-0.1.0/tests/unit/test_adapter_musescore.py +191 -0
  251. mxl_agent-0.1.0/tests/unit/test_adapter_timemap.py +70 -0
  252. mxl_agent-0.1.0/tests/unit/test_adapter_verovio.py +30 -0
  253. mxl_agent-0.1.0/tests/unit/test_adapters_jjazzlab_sng.py +157 -0
  254. mxl_agent-0.1.0/tests/unit/test_analysis_active_key.py +113 -0
  255. mxl_agent-0.1.0/tests/unit/test_analysis_cadence_candidate.py +64 -0
  256. mxl_agent-0.1.0/tests/unit/test_analysis_chord_recognition.py +76 -0
  257. mxl_agent-0.1.0/tests/unit/test_analysis_chord_tone.py +112 -0
  258. mxl_agent-0.1.0/tests/unit/test_analysis_cross_part.py +93 -0
  259. mxl_agent-0.1.0/tests/unit/test_analysis_doubling_polyphony.py +107 -0
  260. mxl_agent-0.1.0/tests/unit/test_analysis_harmonic_rhythm.py +67 -0
  261. mxl_agent-0.1.0/tests/unit/test_analysis_interval_above_bass.py +113 -0
  262. mxl_agent-0.1.0/tests/unit/test_analysis_lint.py +180 -0
  263. mxl_agent-0.1.0/tests/unit/test_analysis_local_key.py +97 -0
  264. mxl_agent-0.1.0/tests/unit/test_analysis_non_chord_tone.py +121 -0
  265. mxl_agent-0.1.0/tests/unit/test_analysis_note_density.py +54 -0
  266. mxl_agent-0.1.0/tests/unit/test_analysis_parallel_motion.py +89 -0
  267. mxl_agent-0.1.0/tests/unit/test_analysis_playability.py +102 -0
  268. mxl_agent-0.1.0/tests/unit/test_analysis_roman_numeral.py +198 -0
  269. mxl_agent-0.1.0/tests/unit/test_analysis_scale_degree.py +121 -0
  270. mxl_agent-0.1.0/tests/unit/test_analysis_syncopation.py +78 -0
  271. mxl_agent-0.1.0/tests/unit/test_analysis_voice_leading.py +90 -0
  272. mxl_agent-0.1.0/tests/unit/test_chord_symbol.py +397 -0
  273. mxl_agent-0.1.0/tests/unit/test_cli.py +94 -0
  274. mxl_agent-0.1.0/tests/unit/test_cli_commands.py +1937 -0
  275. mxl_agent-0.1.0/tests/unit/test_corpus_cross_producer.py +99 -0
  276. mxl_agent-0.1.0/tests/unit/test_duration.py +264 -0
  277. mxl_agent-0.1.0/tests/unit/test_export_edited.py +170 -0
  278. mxl_agent-0.1.0/tests/unit/test_fixture_builder.py +28 -0
  279. mxl_agent-0.1.0/tests/unit/test_generate_accompaniment.py +190 -0
  280. mxl_agent-0.1.0/tests/unit/test_generate_arrangement.py +68 -0
  281. mxl_agent-0.1.0/tests/unit/test_generate_from_midi.py +149 -0
  282. mxl_agent-0.1.0/tests/unit/test_generate_lead_sheet.py +269 -0
  283. mxl_agent-0.1.0/tests/unit/test_generate_reharmonization.py +166 -0
  284. mxl_agent-0.1.0/tests/unit/test_generate_voicing.py +217 -0
  285. mxl_agent-0.1.0/tests/unit/test_json_normalization.py +35 -0
  286. mxl_agent-0.1.0/tests/unit/test_midi_reader.py +145 -0
  287. mxl_agent-0.1.0/tests/unit/test_model_chord_recognition.py +68 -0
  288. mxl_agent-0.1.0/tests/unit/test_model_chord_tones.py +131 -0
  289. mxl_agent-0.1.0/tests/unit/test_model_instrument_ranges.py +64 -0
  290. mxl_agent-0.1.0/tests/unit/test_model_key_signature.py +114 -0
  291. mxl_agent-0.1.0/tests/unit/test_model_layout.py +70 -0
  292. mxl_agent-0.1.0/tests/unit/test_model_merge.py +114 -0
  293. mxl_agent-0.1.0/tests/unit/test_model_non_chord_tone.py +70 -0
  294. mxl_agent-0.1.0/tests/unit/test_model_roman_numeral.py +280 -0
  295. mxl_agent-0.1.0/tests/unit/test_model_semantic_diff.py +205 -0
  296. mxl_agent-0.1.0/tests/unit/test_model_timewise.py +123 -0
  297. mxl_agent-0.1.0/tests/unit/test_mutation_guard.py +138 -0
  298. mxl_agent-0.1.0/tests/unit/test_mxl_multi_score.py +131 -0
  299. mxl_agent-0.1.0/tests/unit/test_mxl_reader.py +75 -0
  300. mxl_agent-0.1.0/tests/unit/test_operation_barlines.py +284 -0
  301. mxl_agent-0.1.0/tests/unit/test_operation_change_duration.py +285 -0
  302. mxl_agent-0.1.0/tests/unit/test_operation_chord_symbol_style.py +136 -0
  303. mxl_agent-0.1.0/tests/unit/test_operation_copy_notes.py +319 -0
  304. mxl_agent-0.1.0/tests/unit/test_operation_create_tuplet.py +340 -0
  305. mxl_agent-0.1.0/tests/unit/test_operation_credit.py +186 -0
  306. mxl_agent-0.1.0/tests/unit/test_operation_delete_note.py +296 -0
  307. mxl_agent-0.1.0/tests/unit/test_operation_direction_spanners.py +346 -0
  308. mxl_agent-0.1.0/tests/unit/test_operation_directions.py +384 -0
  309. mxl_agent-0.1.0/tests/unit/test_operation_envelope.py +76 -0
  310. mxl_agent-0.1.0/tests/unit/test_operation_insert_delete_harmony.py +250 -0
  311. mxl_agent-0.1.0/tests/unit/test_operation_insert_note.py +302 -0
  312. mxl_agent-0.1.0/tests/unit/test_operation_layout.py +313 -0
  313. mxl_agent-0.1.0/tests/unit/test_operation_lyrics.py +294 -0
  314. mxl_agent-0.1.0/tests/unit/test_operation_measure_operations.py +292 -0
  315. mxl_agent-0.1.0/tests/unit/test_operation_merge_tied_notes.py +255 -0
  316. mxl_agent-0.1.0/tests/unit/test_operation_metadata.py +217 -0
  317. mxl_agent-0.1.0/tests/unit/test_operation_move_note.py +270 -0
  318. mxl_agent-0.1.0/tests/unit/test_operation_notations.py +379 -0
  319. mxl_agent-0.1.0/tests/unit/test_operation_rename_part.py +213 -0
  320. mxl_agent-0.1.0/tests/unit/test_operation_repair.py +214 -0
  321. mxl_agent-0.1.0/tests/unit/test_operation_replace_harmony.py +226 -0
  322. mxl_agent-0.1.0/tests/unit/test_operation_replace_note_pitch.py +306 -0
  323. mxl_agent-0.1.0/tests/unit/test_operation_respell_pitch.py +212 -0
  324. mxl_agent-0.1.0/tests/unit/test_operation_score_defaults.py +180 -0
  325. mxl_agent-0.1.0/tests/unit/test_operation_set_attributes.py +304 -0
  326. mxl_agent-0.1.0/tests/unit/test_operation_spanners.py +417 -0
  327. mxl_agent-0.1.0/tests/unit/test_operation_split_note.py +289 -0
  328. mxl_agent-0.1.0/tests/unit/test_operation_transpose_notes.py +269 -0
  329. mxl_agent-0.1.0/tests/unit/test_pagination.py +104 -0
  330. mxl_agent-0.1.0/tests/unit/test_patch.py +251 -0
  331. mxl_agent-0.1.0/tests/unit/test_patch_conflicts.py +207 -0
  332. mxl_agent-0.1.0/tests/unit/test_patch_repair.py +110 -0
  333. mxl_agent-0.1.0/tests/unit/test_pitch.py +138 -0
  334. mxl_agent-0.1.0/tests/unit/test_pitch_microtonal.py +153 -0
  335. mxl_agent-0.1.0/tests/unit/test_plugin_manifest.py +152 -0
  336. mxl_agent-0.1.0/tests/unit/test_query_inspect.py +77 -0
  337. mxl_agent-0.1.0/tests/unit/test_release_artifacts.py +41 -0
  338. mxl_agent-0.1.0/tests/unit/test_render_identity.py +297 -0
  339. mxl_agent-0.1.0/tests/unit/test_revision_history.py +210 -0
  340. mxl_agent-0.1.0/tests/unit/test_score_document.py +30 -0
  341. mxl_agent-0.1.0/tests/unit/test_score_index.py +155 -0
  342. mxl_agent-0.1.0/tests/unit/test_selection.py +165 -0
  343. mxl_agent-0.1.0/tests/unit/test_service_session.py +102 -0
  344. mxl_agent-0.1.0/tests/unit/test_session_merge.py +173 -0
  345. mxl_agent-0.1.0/tests/unit/test_session_selection_store.py +55 -0
  346. mxl_agent-0.1.0/tests/unit/test_skill_wrapper.py +259 -0
  347. mxl_agent-0.1.0/tests/unit/test_timeline.py +406 -0
  348. mxl_agent-0.1.0/tests/unit/test_timeline_transposition.py +58 -0
  349. mxl_agent-0.1.0/tests/unit/test_transaction.py +127 -0
  350. mxl_agent-0.1.0/tests/unit/test_transaction_apply.py +262 -0
  351. mxl_agent-0.1.0/tests/unit/test_transaction_timewise.py +58 -0
  352. mxl_agent-0.1.0/tests/unit/test_validation_compatibility.py +59 -0
  353. mxl_agent-0.1.0/tests/unit/test_validation_fast.py +273 -0
  354. mxl_agent-0.1.0/tests/unit/test_viewer_server.py +550 -0
  355. mxl_agent-0.1.0/tests/unit/test_workspace.py +134 -0
  356. mxl_agent-0.1.0/tests/unit/test_writer.py +66 -0
  357. mxl_agent-0.1.0/tests/unit/test_xml_canonical.py +56 -0
  358. mxl_agent-0.1.0/tests/unit/test_xml_detect.py +38 -0
  359. mxl_agent-0.1.0/tests/unit/test_xml_parser.py +66 -0
  360. mxl_agent-0.1.0/tests/unit/test_xml_schema.py +105 -0
  361. mxl_agent-0.1.0/tests/unit/test_zip_compare.py +60 -0
  362. mxl_agent-0.1.0/tests/unit/test_zip_safety_helpers.py +31 -0
  363. mxl_agent-0.1.0/uv.lock +1359 -0
@@ -0,0 +1,309 @@
1
+ ---
2
+ name: mxl-agent
3
+ description: Inspect, edit, and validate MusicXML and compressed MXL scores using the local mxl-agent engine. Use for .mxl, .musicxml, or .xml score files, chord-symbol edits, note-pitch edits, transposition, score summaries/measure inspection, MusicXML schema/temporal validation, or exporting an edited score to a new file. Not for general MIDI, audio, DAW, or PDF-only tasks that never touch a MusicXML file.
4
+ compatibility: Requires the mxl-agent CLI in this project (invoked via `uv run mxl-agent`, or on PATH). No network access. MuseScore (`musescore` command) and Verovio (`render`/`playback`) are optional, each unavailable if `doctor` reports it missing.
5
+ allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/mxl-agent.sh *)
6
+ ---
7
+
8
+ # mxl-agent
9
+
10
+ A deterministic local engine for MusicXML/MXL scores. It owns all XML structure, arithmetic, and
11
+ file I/O. You interpret the user's musical intent and choose typed operations; you never write or
12
+ edit MusicXML by hand.
13
+
14
+ ## Hard rules
15
+
16
+ - **Never** read a whole score file into your context (no `cat`, no opening it as a text file).
17
+ Every command below returns a small, bounded JSON summary instead.
18
+ - **Never** author or emit a MusicXML document, or an XML fragment reconstructing one, as your own
19
+ text output.
20
+ - **Never** overwrite the user's original file. `export` always writes a new path.
21
+ - Only call `${CLAUDE_SKILL_DIR}/scripts/mxl-agent.sh <command> ...`. Do not invoke `mxl-agent`,
22
+ `uv`, or `python` directly, and do not construct shell strings around the wrapper's arguments.
23
+ - If a selection could match more than one thing (see "Ambiguity" below), stop and ask, or narrow
24
+ the selection. Do not guess and apply anyway.
25
+
26
+ ## Workflow for an editing request
27
+
28
+ 1. **Open** the score into a session (`session open`) if you don't already have a `session_id`
29
+ for it in this conversation. Reuse the same session for follow-up edits instead of reopening.
30
+ 2. **Inspect** only the region you need (`inspect summary`, then `inspect measures` or
31
+ `inspect events` narrowed to the relevant part/measures). Use the returned `event_id` values
32
+ in later steps rather than re-deriving positions yourself.
33
+ 3. **Plan** (`plan`) the operation first. Read `changed[].before`/`after` and `warnings`. If
34
+ `changed` is empty, has more than one entry when the user asked for one thing, or the command
35
+ fails with an ambiguity error, stop and clarify with the user rather than applying.
36
+ 4. **Apply** (`apply`) only once the plan matches what the user asked for. Use the exact same
37
+ operation JSON you planned with.
38
+ 5. **Validate** (`validate`) after applying. If `status` is `"failed"`, report the issues; do not
39
+ silently continue.
40
+ 6. **Export** (`export --output <new-path>`) to a new file when the user wants a saved result.
41
+ Never pass the original input path as `--output`.
42
+ 7. Summarize what changed in plain language (part, measure, before -> after) using the `plan`/
43
+ `apply` result. Do not paste raw XML.
44
+
45
+ For a read-only question ("what's the form of this tune", "what chords are in bars 9-16"), stop
46
+ after inspecting -- do not plan or apply anything.
47
+
48
+ `undo` reverts the session's current revision to its parent, one step. Offer it if the user asks
49
+ to revert an edit.
50
+
51
+ ## Commands
52
+
53
+ All commands take `--json` (always pass it) and `--workspace-root <path>` (omit it to use the
54
+ default `.mxl-agent/workspaces` under the current project). Full parameter/error reference:
55
+ `references/command-catalog.md`.
56
+
57
+ ```bash
58
+ mxl-agent.sh session open <path-to-score> [--score SELECTOR] --json
59
+ mxl-agent.sh session status <session_id> --json
60
+ mxl-agent.sh session checkout <session_id> (--revision N | --checkpoint NAME) --json
61
+ mxl-agent.sh session revisions <session_id> --json
62
+ mxl-agent.sh create lead-sheet --spec '<lead sheet spec JSON>' --json
63
+ mxl-agent.sh create from-midi <midi-path> [--quantization NAME] [--beats N] [--fifths N] --json
64
+ mxl-agent.sh chord-chart parse --text '<chord chart text>' [--beats N] [--beat-type N] --json
65
+ mxl-agent.sh voice chord --symbol '<chord symbol>' --ranges '<voice ranges JSON>' [--profile classical|jazz] --json
66
+ mxl-agent.sh voice arrange --symbol '<chord symbol>' --instruments '<names JSON array>' [--profile classical|jazz] --json
67
+ mxl-agent.sh reharmonize check <session_id> --plan '<reharmonization plan JSON>' [--profile classical|jazz] --json
68
+ mxl-agent.sh accompany bass --spans '<chord spans JSON>' --range '<voice range JSON>' --json
69
+ mxl-agent.sh accompany guide-tones --spans '<chord spans JSON>' --range '<voice range JSON>' [--role ROLE ...] --json
70
+ mxl-agent.sh accompany comping --spans '<chord spans JSON>' --ranges '<voice ranges JSON>' --pulse '<n/d>' --json
71
+
72
+ mxl-agent.sh inspect summary <session_id> --json
73
+ mxl-agent.sh inspect parts <session_id> --json
74
+ mxl-agent.sh inspect measures <session_id> [--part-id P1] [--limit N] [--cursor C] --json
75
+ mxl-agent.sh inspect events <session_id> --part-id P1 [--limit N] [--cursor C] --json
76
+
77
+ mxl-agent.sh plan <session_id> --operation '<operation JSON>' --json
78
+ mxl-agent.sh apply <session_id> --operation '<operation JSON>' --json
79
+ mxl-agent.sh inverse <session_id> --operation '<operation JSON>' --json
80
+ mxl-agent.sh plan-patch <session_id> --patch '<patch JSON>' --json
81
+ mxl-agent.sh apply-patch <session_id> --patch '<patch JSON>' --json
82
+ mxl-agent.sh merge <session_id> --revision-a N --revision-b N --json
83
+ mxl-agent.sh analyze <session_id> --analysis NAME [--selection '<JSON>'] --json
84
+ mxl-agent.sh lint <session_id> [--selection '<JSON>'] --json
85
+ mxl-agent.sh repair <session_id> --fix <fix_id> --json
86
+ mxl-agent.sh repair-plan <session_id> --fix <fix_id> [--fix <fix_id> ...] --json
87
+ mxl-agent.sh repair-apply <session_id> --fix <fix_id> [--fix <fix_id> ...] --json
88
+ mxl-agent.sh undo <session_id> --json
89
+ mxl-agent.sh redo <session_id> --json
90
+ mxl-agent.sh checkpoint create <session_id> --name NAME [--revision N] --json
91
+ mxl-agent.sh checkpoint list <session_id> --json
92
+ mxl-agent.sh validate <session_id> [--revision N] --json
93
+ mxl-agent.sh compat-check <session_id> --target-version 3.1|4.0 [--revision N] --json
94
+ mxl-agent.sh diff <session_id> --json
95
+ mxl-agent.sh semantic-diff <session_id> [--from-revision N] [--to-revision N] --json
96
+ mxl-agent.sh log <session_id> --json
97
+ mxl-agent.sh export <session_id> --output <new-path> --json
98
+ mxl-agent.sh render <session_id> --output <new-svg-path> [--page N] [--revision N] --json
99
+ mxl-agent.sh playback <session_id> --output <new-midi-path> [--revision N] --json
100
+ mxl-agent.sh musescore check <session_id> [--revision N] --json
101
+ mxl-agent.sh musescore export <session_id> --output <path> --format pdf|png|mid|wav|musicxml|mxl [--revision N] --json
102
+ mxl-agent.sh mei export <session_id> --output <path> [--revision N] --json
103
+ mxl-agent.sh mei render <mei-path> --output <svg-path> [--page N] --json
104
+ mxl-agent.sh selection current <session_id> --json
105
+ ```
106
+
107
+ Every result is one JSON object on stdout: `{"ok": true, ...}` or, on an expected failure,
108
+ `{"ok": false, "error": {"code": ..., "message": ...}}` with a non-zero exit code -- never a raw
109
+ traceback. Treat any non-zero exit as "the operation did not happen."
110
+
111
+ ## Generating a new score
112
+
113
+ Two-stage rule: propose a compact typed plan, never author or paste MusicXML. Full shapes:
114
+ `references/command-catalog.md`. `create from-midi` is the exception (MIDI in, not JSON) and is
115
+ explicitly lossy -- read its `report` before trusting the result.
116
+
117
+ - `create lead-sheet --spec '<JSON>'` builds a brand-new single-part score (metadata, meter, key,
118
+ measures of melody/rests and harmony) and opens it as a new session, exactly like
119
+ `session open`. Doesn't pad short measures itself -- run `lint`/`repair` on the result.
120
+ - `chord-chart parse --text '<chart>'` turns a small text dialect (bars on `|`, whitespace-
121
+ separated chords, `%` repeats the previous bar) into the `{"offset_q", "symbol"}` list `create
122
+ lead-sheet`'s `measures[].harmony` expects. Session-independent.
123
+ - `voice chord --symbol '<chord>' --ranges '<JSON>'` voices a chord across ordered voice ranges
124
+ (pitch pairs or instrument names) -- real chord tones only (tensions under `--profile jazz`), no
125
+ crossing, doubling avoided until forced; raises `VOICING_UNSATISFIABLE` naming the first
126
+ unplaceable voice. `voice arrange --symbol '<chord>' --instruments '<names JSON>'` is the same
127
+ engine for a named ensemble, also reporting each instrument's transposed written pitch.
128
+ Session-independent.
129
+ - `reharmonize check <session_id> --plan '<JSON>'` validates a reharmonization plan you propose
130
+ (frozen measures, target positions, candidate chords, optional rationale) -- never invents a
131
+ chord itself. Reports structural `issues` plus an informational `melody_compatibility` per
132
+ candidate (not pass/fail). Read-only.
133
+ - `accompany bass`/`guide-tones` materialize one note per `--spans` entry (ordered
134
+ `{measure_ordinal, offset_q, duration_q, symbol}`), the nearest chord-tone role to the previous
135
+ note, within one `--range`. `accompany comping` reuses `voice chord`'s engine to stab a full
136
+ voicing every `--pulse`. All raise `ACCOMPANIMENT_UNSATISFIABLE` naming the failing span.
137
+ Session-independent.
138
+
139
+ ## Candidate branches
140
+
141
+ `undo`/`checkout` then a *different* `apply` creates a real branch -- revision numbers are never
142
+ reused (Slice 7.4). To generate/compare alternatives without disturbing the current session:
143
+ `checkout` to the shared starting revision, `apply` each candidate as its own branch, `session
144
+ revisions` to list them, `semantic-diff --from-revision --to-revision` to compare, and
145
+ `validate`/`render`/`playback --revision N` to audition one without moving `current_revision`.
146
+ Accept = `checkout` to that revision; discard = do nothing.
147
+
148
+ ## Operations (for `plan`/`apply --operation`)
149
+
150
+ The operation envelope's shape and every operation's `selection`/`parameters` fields are in
151
+ `references/selectors.md`. Currently implemented operations:
152
+
153
+ - `replace_harmony` -- change one existing chord symbol.
154
+ - `insert_harmony` -- add a chord symbol where none exists yet at that offset.
155
+ - `delete_harmony` -- remove one existing chord symbol.
156
+ - `replace_note_pitch` -- change one note's pitch (written, or sounding pitch for transposing
157
+ instruments).
158
+ - `respell_pitch` -- enharmonic respelling of one note (same sound, different spelling), explicit
159
+ or key-aware.
160
+ - `transpose_notes` -- transpose every note a selection resolves to by a fixed number of
161
+ semitones.
162
+ - `insert_note` -- insert a note or rest at an exact onset in an existing voice (append after its
163
+ current content, or fill a matching rest).
164
+ - `delete_note` -- delete one note, rest, or grace note, either outright or replaced with an
165
+ equal-duration rest to conserve the voice's total duration.
166
+ - `change_duration` -- change one note or rest's duration, rejecting a change that would desync
167
+ another voice unless explicitly told to compensate for it.
168
+ - `split_note` -- split one note or rest into two, tied together with correct start/stop pairing
169
+ if pitched.
170
+ - `merge_tied_notes` -- merge a whole tie chain into one note, when it's entirely within one
171
+ measure.
172
+ - `create_tuplet` -- convert a consecutive run of notes/rests into a tuplet (time-modification +
173
+ bracket notation).
174
+ - `move_note` -- move one note/rest to a different staff and/or voice, conserving durations on
175
+ both ends.
176
+ - `copy_notes` -- copy a consecutive note/rest range to a destination voice, literally or
177
+ transposed (a "sequence").
178
+ - `set_attributes` -- change key/time/clef/transpose starting at one measure; reports sounding
179
+ consequences of a transposition change.
180
+ - `set_lyric` / `delete_lyric` -- set or remove a lyric verse (text, syllabic, elision, extend)
181
+ on one note.
182
+ - `insert_direction` / `delete_direction` -- add or remove a words/dynamics/metronome/segno/coda
183
+ marking at an exact offset; the `sound` playback attributes stay separate from what's printed.
184
+ - `add_notation` / `remove_notation` -- add or remove an articulation, ornament, or technical
185
+ mark on one note (idempotent).
186
+ - `set_notehead` -- change one note's notehead shape.
187
+ - `start_slur` / `stop_slur` -- open/close a slur, auto-allocating/pairing its number and
188
+ validating the stop follows the start.
189
+ - `insert_measure` / `delete_measure` / `duplicate_measure` / `renumber_measures` -- whole-score
190
+ structural edits, always applied to every part at once to keep them in sync.
191
+ - `set_barline` -- set or clear one measure's left/right barline: style, a repeat sign
192
+ (forward/backward, optional play count), and/or a first/second-ending bracket. No
193
+ `bar_style`/`repeat`/`ending` given clears it back to default.
194
+ - `rename_part` -- change one part's printed name and/or abbreviation in the part-list.
195
+ - `set_metadata` -- change score-wide title/movement/creator/rights/source metadata.
196
+ - `set_measure_layout` -- force/forbid a system or page break at one measure, and/or set its
197
+ printed width.
198
+ - `set_measures_per_system` -- deterministic helper: system breaks exactly periodic (every N
199
+ measures) over a range, all parts at once.
200
+ - `remove_dangling_tie` / `pad_short_measure` -- safe automatic repairs for a `lint` finding (see
201
+ below); always take a `fix_id` from `lint`'s output rather than hand-built parameters.
202
+
203
+ Chord-symbol vocabulary, spelling policies, and written-vs-sounding pitch are covered in
204
+ `references/musical-semantics.md`.
205
+
206
+ ## Analysis (for `analyze`)
207
+
208
+ Read-only, never mutates -- no `plan`/`apply` two-step needed. Full parameter/result reference:
209
+ `references/command-catalog.md`.
210
+
211
+ - `active_key` -- the explicit key signature (fifths, mode) in effect at each address the
212
+ selection resolves to. Locally inferred keys (for passages with no explicit signature) aren't
213
+ implemented yet.
214
+ - `scale_degree` -- key-relative scale degree (1-7, mode-aware) and chromatic alteration for each
215
+ selected note. Needs an event-granular selection (one note, or a note range) -- narrow with
216
+ staff/voice/offset or `event_ids` rather than a bare measure selection; same requirement below
217
+ wherever "event-granular" is mentioned.
218
+ - `chord_tone` -- classifies each selected note against the chord symbol active at its onset:
219
+ chord tone (with a role), available tension (jazz profile only), non-chord-tone, or unresolved
220
+ (no harmony/ambiguous polychord/Roman-numeral harmony). Event-granular. Never guesses an
221
+ unrelated chord -- only classifies against a chord symbol the score already has.
222
+ - `roman_numeral` -- Roman-numeral (`"V7"`, `"bVI"`) or Nashville-number (`"5m7"`, `"b6"`) label
223
+ for the chord symbol active at each address, relative to the active key. Flags (but doesn't
224
+ further classify) chromatic/borrowed chords via `is_diatonic`. Flexible measure-or-event
225
+ granularity, like `active_key`.
226
+ - `harmonic_rhythm` -- the ordered sequence of chord-symbol changes in a part, each with its
227
+ exact onset and (same-measure only) duration.
228
+ - `cadence_candidate` -- bare root-motion cadence pattern matches (authentic/plagal/half/
229
+ deceptive) between consecutive chord pairs -- candidates, not judgments; more matches than real
230
+ cadences is normal since it can't see phrase position or meter.
231
+ - `non_chord_tone` -- for an already-classified non-chord tone (see `chord_tone`), which figure it
232
+ resembles (passing, neighbor, suspension, anticipation, appoggiatura, pedal, unknown) from its
233
+ melodic approach/departure alone, no metric reasoning. Event-granular.
234
+ - `voice_leading` -- common tones and guide-tone (3rd/7th) motion between consecutive chord pairs.
235
+ Tendency-tone resolution, parallel 5ths/8ves, spacing, and voice crossing aren't implemented yet.
236
+ - `playability` -- checks each note's written/sounding pitch against a named instrument's typical
237
+ range (`parameters.instrument`, e.g. `"violin"`, `"clarinet_bb"` -- a small curated set, not a
238
+ full orchestration reference). Event-granular.
239
+ - `note_density` -- notes per quarter note in each resolved measure (same flexible granularity
240
+ as `active_key`).
241
+ - `syncopation` -- whether each selected note starts off the beat and sustains past the next
242
+ beat boundary. Motives, polyphony, and doubling aren't implemented yet.
243
+
244
+ ## Lint and repair
245
+
246
+ `lint` scans the score (or the selection you narrow it to) for a small, stable catalog of
247
+ findings, each with a `finding_id`, `severity`, location, human-readable `message`, and
248
+ `fixable`/`fix_id`:
249
+
250
+ - `dangling-tie` (error, always fixable) -- a `<tie type="start">` never closed, or a
251
+ `<tie type="stop">` with no matching prior start.
252
+ - `measure-too-short` (warning, always fixable) -- a single-voice measure short of its active
253
+ time signature's expected length. A pickup measure or a measure with more than one voice is
254
+ never flagged.
255
+ - `voice-duration-mismatch` (warning, **not** fixable) -- two voices in the same measure disagree
256
+ on total duration; report it, don't repair it, since which voice is wrong isn't decidable from
257
+ the mismatch alone.
258
+
259
+ For a fixable finding, pass its exact `fix_id` to `repair --fix` -- never hand-construct it.
260
+ `repair` applies through the same typed, footprint-guarded machinery as `plan`/`apply`, producing
261
+ a normal revision `undo` can revert.
262
+
263
+ To fix several findings together, repeat `--fix` on `repair-plan`/`repair-apply` instead of
264
+ calling `repair` per finding. `repair-plan` dry-runs the group (writes nothing), reporting
265
+ `PATCH_CONFLICT` if two fixes are incompatible. `repair-apply` commits the group atomically, but
266
+ each fix still gets its own revision/`log` entry, so `undo` reverts one at a time.
267
+
268
+ ## Rendering
269
+
270
+ `render` produces a static SVG page, `playback` a MIDI file -- neither changes the score. Both
271
+ need Verovio (`VEROVIO_UNAVAILABLE` if `doctor` reports it missing). Only whole-score output
272
+ (`render --page N`, default `1`) -- no selection-level rendering yet.
273
+
274
+ `musescore check`/`export` (a second, independent optional integration) run an open/import
275
+ diagnostic, or export to PDF/PNG/MIDI/WAV/MusicXML, through a real MuseScore CLI --
276
+ `MUSESCORE_UNAVAILABLE` if `doctor` finds none. Its output is diagnostic/export-only, never
277
+ written back into a session.
278
+
279
+ `mei export`/`render` (through Verovio) convert to MEI or render a standalone MEI file to SVG --
280
+ interoperability only, never an editable input.
281
+
282
+ If the user has the local viewer (`preview`) open and clicks a note there, `selection current`
283
+ returns `{"addresses": [...]}` -- the fully resolved locator(s) they last selected (`part_id`,
284
+ `measure_ordinal`, `event_id`, etc.), or `{"addresses": []}` if nothing's been selected yet.
285
+ Build a `--selection` from it as `{"event_ids": [<address.event_id>, ...]}`. Only note-level
286
+ clicks are supported (a chord or measure click resolves to whichever note element was actually
287
+ clicked).
288
+
289
+ ## Ambiguity
290
+
291
+ If a selection resolves to zero or more than one target when the user meant exactly one, the
292
+ operation raises a structured `*_AMBIGUOUS` or `*_NOT_FOUND` error (or, for note-range
293
+ operations, `plan` legitimately returns multiple `changed` entries -- that's normal for a
294
+ *range*, not an error). When it's an error: narrow the selection (add a measure/voice/staff/
295
+ offset, or use an `event_id` from a prior `inspect` call) or ask the user which one they meant.
296
+ Never retry with a broader selection hoping it "just picks one" -- it won't; ambiguity blocks
297
+ mutation by design.
298
+
299
+ ## Safety and preservation
300
+
301
+ - The original file is never touched. `session open` copies it into an immutable workspace.
302
+ - Every `apply` is mutation-guarded: an operation that changes anything outside its declared
303
+ footprint is rejected before it's ever written.
304
+ - `apply` always creates a new revision; `undo` is always available for the most recent one.
305
+ - MusicXML 3.1 and 4.0 both supported: `validate` auto-selects the matching schema; `compat-check
306
+ --target-version` cross-validates against the other version.
307
+ - An `.mxl` with more than one score ("opus") needs `--score` on `session open`;
308
+ `MULTIPLE_SCORE_ROOTS_AMBIGUOUS` lists the available paths/indices.
309
+ - Full invariants: `references/preservation-and-safety.md`.
@@ -0,0 +1,168 @@
1
+ {
2
+ "schema_version": "1.0",
3
+ "skill": "mxl-agent",
4
+ "note": "Structured eval cases per Section 18.1/14.6. Trigger and workflow cases are evaluated by a clean-context Claude Code eval run (not by this repo's deterministic test suite). The end-to-end command sequences below are also covered directly, deterministically, by tests/unit/test_cli_commands.py and tests/unit/test_skill_wrapper.py, which check the tooling those transcripts depend on actually works.",
5
+
6
+ "should_trigger": [
7
+ {
8
+ "id": "trigger-open-and-transpose",
9
+ "prompt": "Open chart.mxl and transpose the melody up a whole step.",
10
+ "expect_skill_used": true
11
+ },
12
+ {
13
+ "id": "trigger-chord-edit",
14
+ "prompt": "In bars.musicxml, change the chord at measure 3 to Dm7.",
15
+ "expect_skill_used": true
16
+ },
17
+ {
18
+ "id": "trigger-inspect-only",
19
+ "prompt": "What key is this piece in? It's in song.musicxml.",
20
+ "expect_skill_used": true
21
+ },
22
+ {
23
+ "id": "trigger-mxl-mention",
24
+ "prompt": "Can you validate this .mxl file for me?",
25
+ "expect_skill_used": true
26
+ },
27
+ {
28
+ "id": "trigger-note-pitch",
29
+ "prompt": "The second note in measure 5 of lead.musicxml should be an F sharp, not F natural.",
30
+ "expect_skill_used": true
31
+ }
32
+ ],
33
+
34
+ "should_not_trigger": [
35
+ {
36
+ "id": "no-trigger-daw-mixing",
37
+ "prompt": "How do I set up a sidechain compressor in my DAW?",
38
+ "expect_skill_used": false
39
+ },
40
+ {
41
+ "id": "no-trigger-pdf-only",
42
+ "prompt": "Extract the text from this PDF scan of sheet music.",
43
+ "expect_skill_used": false
44
+ },
45
+ {
46
+ "id": "no-trigger-plain-midi",
47
+ "prompt": "Play this MIDI file and tell me what instrument sound 12 is in General MIDI.",
48
+ "expect_skill_used": false
49
+ },
50
+ {
51
+ "id": "no-trigger-generic-xml",
52
+ "prompt": "Validate this config.xml against our internal schema.",
53
+ "expect_skill_used": false
54
+ },
55
+ {
56
+ "id": "no-trigger-theory-only",
57
+ "prompt": "What's the difference between a Lydian and Ionian mode?",
58
+ "expect_skill_used": false
59
+ }
60
+ ],
61
+
62
+ "workflow_adherence": [
63
+ {
64
+ "id": "workflow-dry-run-before-apply",
65
+ "description": "For any edit request, the transcript must show a `plan` call before the matching `apply` call with the same operation JSON.",
66
+ "assertion": "sequence contains plan(op) immediately or shortly before apply(op) with equal operation/selection/parameters"
67
+ },
68
+ {
69
+ "id": "workflow-inspect-before-edit",
70
+ "description": "Before constructing a selection by measure/voice/offset, the transcript shows an `inspect summary` or `inspect measures`/`inspect events` call establishing that address -- not a guessed measure_ordinal.",
71
+ "assertion": "an inspect_* call for the relevant part/measure precedes the first plan/apply call"
72
+ },
73
+ {
74
+ "id": "workflow-validate-after-apply",
75
+ "description": "After the final `apply` of a session, a `validate` call appears before reporting success to the user.",
76
+ "assertion": "validate(session_id) appears after the last apply(session_id) and before the final assistant summary"
77
+ },
78
+ {
79
+ "id": "workflow-no-raw-xml",
80
+ "description": "No tool call or assistant message contains a `<score-partwise` or `<score-timewise` opening tag, or any multi-element MusicXML fragment reconstructed by the model.",
81
+ "assertion": "absence check across the full transcript"
82
+ },
83
+ {
84
+ "id": "workflow-ambiguity-stops",
85
+ "description": "When `plan` returns a `*_AMBIGUOUS` error, the assistant does not retry with a broader/different selection hoping for a single match -- it asks the user or narrows using data already inspected.",
86
+ "assertion": "no plan/apply retry with a materially different selection immediately following an *_AMBIGUOUS error, without an intervening clarification"
87
+ }
88
+ ],
89
+
90
+ "output_correctness": [
91
+ {
92
+ "id": "correctness-chord-edit-e2e",
93
+ "description": "End-to-end: one chord-symbol edit.",
94
+ "fixture": "fixtures/chord_symbols.musicxml",
95
+ "steps": [
96
+ "session open fixtures/chord_symbols.musicxml",
97
+ "inspect summary <session_id>",
98
+ "inspect measures <session_id> --part-id P1",
99
+ "plan <session_id> --operation '{\"operation\":\"replace_harmony\",\"selection\":{\"part_ids\":[\"P1\"],\"measure_ordinals\":[0]},\"parameters\":{\"symbol\":\"Cm7\"},...}'",
100
+ "apply <session_id> --operation '<same operation>'",
101
+ "validate <session_id>",
102
+ "export <session_id> --output <new path>"
103
+ ],
104
+ "assert": [
105
+ "plan.changed[0].before == 'Cmaj7'",
106
+ "plan.changed[0].after == 'Cm7'",
107
+ "apply.revision_after == apply.revision_before + 1",
108
+ "validate.status == 'passed'",
109
+ "exported file reopens and contains a Cm7 harmony at part P1 measure 0"
110
+ ],
111
+ "deterministic_test": "tests/unit/test_operation_replace_harmony.py, tests/unit/test_cli_commands.py::test_plan_then_apply_then_validate_then_export_then_undo"
112
+ },
113
+ {
114
+ "id": "correctness-transpose-e2e",
115
+ "description": "End-to-end: transpose a note range up a whole step and verify both written and sounding pitch.",
116
+ "fixture": "fixtures/divisions_change.musicxml",
117
+ "steps": [
118
+ "session open fixtures/divisions_change.musicxml",
119
+ "inspect events <session_id> --part-id P1",
120
+ "plan <session_id> --operation '{\"operation\":\"transpose_notes\",\"selection\":{\"part_ids\":[\"P1\"],\"measure_ordinals\":[0]},\"parameters\":{\"semitones\":2},...}'",
121
+ "apply <session_id> --operation '<same operation>'",
122
+ "validate <session_id>"
123
+ ],
124
+ "assert": [
125
+ "every changed note's written step/octave shifted up a whole step",
126
+ "unselected measure(s) are byte-for-byte unchanged (diff.changed_measures excludes them)",
127
+ "validate.status == 'passed'"
128
+ ],
129
+ "deterministic_test": "tests/unit/test_operation_transpose_notes.py"
130
+ }
131
+ ],
132
+
133
+ "efficiency": [
134
+ {
135
+ "id": "efficiency-token-bound",
136
+ "description": "For the chord-edit and transpose scenarios above, total tool-result bytes returned to the model context should stay under a few KB per call (Section 19.1's 32 KiB/200-item caps), and no call should return a full document.",
137
+ "measurement": "sum of stdout bytes across all mxl-agent.sh invocations in the transcript"
138
+ },
139
+ {
140
+ "id": "efficiency-no-redundant-open",
141
+ "description": "The same score is not reopened into a new session mid-conversation when an open session_id for it is already known.",
142
+ "measurement": "count of session_open calls per distinct input path within one conversation"
143
+ }
144
+ ],
145
+
146
+ "safety": [
147
+ {
148
+ "id": "safety-malformed-archive-fixture",
149
+ "description": "session open on a hostile/malformed .mxl (zip-slip, encrypted entry, compression bomb) must fail with a structured ZIP_* error and must not mutate anything or crash the wrapper.",
150
+ "deterministic_test": "tests/security/test_mxl_safety.py, tests/security/test_zip_safety.py"
151
+ },
152
+ {
153
+ "id": "safety-ambiguous-selector-no-mutation",
154
+ "description": "plan/apply on an ambiguous selection must not write any revision.",
155
+ "deterministic_test": "tests/unit/test_operation_replace_harmony.py::test_ambiguous_target_without_offset_raises, tests/unit/test_transaction.py"
156
+ },
157
+ {
158
+ "id": "safety-stale-revision-rejected",
159
+ "description": "apply with a stale base_revision must fail with REVISION_CONFLICT and must not overwrite the newer revision.",
160
+ "deterministic_test": "tests/unit/test_transaction.py::test_stale_base_revision_raises_revision_conflict"
161
+ },
162
+ {
163
+ "id": "safety-wrapper-rejects-unknown-command",
164
+ "description": "The wrapper script refuses any first argument outside its allowlist, without forwarding it to a shell.",
165
+ "deterministic_test": "tests/unit/test_skill_wrapper.py"
166
+ }
167
+ ]
168
+ }
@@ -0,0 +1,70 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <score-partwise version="4.0">
3
+ <part-list>
4
+ <score-part id="P1">
5
+ <part-name>Lead Sheet</part-name>
6
+ </score-part>
7
+ </part-list>
8
+ <part id="P1">
9
+ <measure number="1">
10
+ <attributes>
11
+ <divisions>1</divisions>
12
+ <key>
13
+ <fifths>0</fifths>
14
+ </key>
15
+ <time>
16
+ <beats>4</beats>
17
+ <beat-type>4</beat-type>
18
+ </time>
19
+ <clef>
20
+ <sign>G</sign>
21
+ <line>2</line>
22
+ </clef>
23
+ </attributes>
24
+ <harmony>
25
+ <root>
26
+ <root-step>C</root-step>
27
+ </root>
28
+ <kind text="maj7">major-seventh</kind>
29
+ </harmony>
30
+ <note>
31
+ <pitch>
32
+ <step>C</step>
33
+ <octave>4</octave>
34
+ </pitch>
35
+ <duration>4</duration>
36
+ <type>whole</type>
37
+ </note>
38
+ </measure>
39
+ <measure number="2">
40
+ <harmony>
41
+ <root>
42
+ <root-step>D</root-step>
43
+ </root>
44
+ <kind text="m7">minor-seventh</kind>
45
+ </harmony>
46
+ <note>
47
+ <pitch>
48
+ <step>D</step>
49
+ <octave>4</octave>
50
+ </pitch>
51
+ <duration>2</duration>
52
+ <type>half</type>
53
+ </note>
54
+ <harmony>
55
+ <root>
56
+ <root-step>G</root-step>
57
+ </root>
58
+ <kind text="7">dominant</kind>
59
+ </harmony>
60
+ <note>
61
+ <pitch>
62
+ <step>G</step>
63
+ <octave>4</octave>
64
+ </pitch>
65
+ <duration>2</duration>
66
+ <type>half</type>
67
+ </note>
68
+ </measure>
69
+ </part>
70
+ </score-partwise>
@@ -0,0 +1,53 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <score-partwise version="4.0">
3
+ <part-list>
4
+ <score-part id="P1">
5
+ <part-name>Music</part-name>
6
+ </score-part>
7
+ </part-list>
8
+ <part id="P1">
9
+ <measure number="1">
10
+ <attributes>
11
+ <divisions>1</divisions>
12
+ <key>
13
+ <fifths>0</fifths>
14
+ </key>
15
+ <time>
16
+ <beats>4</beats>
17
+ <beat-type>4</beat-type>
18
+ </time>
19
+ <clef>
20
+ <sign>G</sign>
21
+ <line>2</line>
22
+ </clef>
23
+ </attributes>
24
+ <note>
25
+ <pitch>
26
+ <step>C</step>
27
+ <octave>4</octave>
28
+ </pitch>
29
+ <duration>1</duration>
30
+ <type>quarter</type>
31
+ </note>
32
+ <note>
33
+ <pitch>
34
+ <step>D</step>
35
+ <octave>4</octave>
36
+ </pitch>
37
+ <duration>3</duration>
38
+ <type>half</type>
39
+ <dot/>
40
+ </note>
41
+ </measure>
42
+ <measure number="2">
43
+ <attributes>
44
+ <divisions>2</divisions>
45
+ </attributes>
46
+ <note>
47
+ <rest/>
48
+ <duration>8</duration>
49
+ <type>whole</type>
50
+ </note>
51
+ </measure>
52
+ </part>
53
+ </score-partwise>