PakettiMCP: non-wedging reload + watchdog self-heal + structured eval… #1520
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Package Paketti as .XRNX | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| branches: | |
| - master | |
| jobs: | |
| # ── Job 0: Validate — fail FAST on anything that crashes/degrades a real load ── | |
| # Duplicate add_midi_mapping/add_keybinding aborts the whole tool in Renoise | |
| # ("'X' was already added") — this shipped once and broke users. This job runs | |
| # the registration harness and fails the build on any duplicate or brittle file, | |
| # on every push AND pull_request. create-release needs it, so a duplicate can | |
| # never produce a release / .xrnx. | |
| validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v2 | |
| with: | |
| submodules: false | |
| - name: Install luajit | |
| run: sudo apt-get update && sudo apt-get install -y luajit | |
| - name: Check duplicate registrations + brittle files | |
| run: python3 .spine/check.py "$GITHUB_WORKSPACE" | |
| # ── Job 1: Create a shared release (tag + GitHub release) ────────── | |
| create-release: | |
| needs: validate | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| tag_name: ${{ steps.gen.outputs.tag_name }} | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v2 | |
| with: | |
| submodules: false | |
| - name: Generate Tag Name | |
| id: gen | |
| run: | | |
| TIMESTAMP=$(date +'%Y-%m-%d_%H-%M-%S') | |
| echo "tag_name=$TIMESTAMP" >> $GITHUB_OUTPUT | |
| - name: Create and Push Tag | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git config --local user.email "action@github.com" | |
| git config --local user.name "GitHub Action" | |
| git tag "${{ steps.gen.outputs.tag_name }}" | |
| git push origin "${{ steps.gen.outputs.tag_name }}" | |
| - name: Create GitHub Release | |
| id: create_release | |
| uses: actions/create-release@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ steps.gen.outputs.tag_name }} | |
| release_name: Release ${{ steps.gen.outputs.tag_name }} | |
| body: | | |
| **Renoise 3.2 – 3.5+** → download **Paketti** (standard .xrnx) | |
| **Renoise 3.1.x** → download **Paketti 3.1** (Paketti31 .xrnx) — installable side-by-side | |
| Support Paketti development: https://github.com/sponsors/esaruoho | |
| draft: false | |
| prerelease: false | |
| # ── Job 2: Standard package (API 6 – Renoise 3.2+) ──────────────── | |
| package-api6: | |
| needs: create-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v2 | |
| with: | |
| submodules: false | |
| - name: Check Lua for forbidden tokens | |
| run: | | |
| if grep -R -n -E 'goto continue|::continue::' --include '*.lua' .; then | |
| echo "::error::Forbidden token found in Lua files. Please remove 'goto continue' or '::continue::'." | |
| exit 1 | |
| fi | |
| - name: Remove definitions folder | |
| run: rm -rf org.lackluster.Paketti.xrnx/definitions | |
| - name: Set filename | |
| run: | | |
| echo "FILENAME=org.lackluster.Paketti_V3.54_${{ needs.create-release.outputs.tag_name }}.xrnx" >> $GITHUB_ENV | |
| - name: Zip XRNX Package | |
| run: | | |
| zip -r "${{ env.FILENAME }}" . \ | |
| -x "*.git*" "*.github*" "docs/*" ".spine/*" \ | |
| "preferences.xml" \ | |
| "preferencesDynamicView.xml" \ | |
| "definitions/*" \ | |
| "manual/*" \ | |
| "Presets/v31/*" \ | |
| "Presets/backup_v33_v34/*" \ | |
| ".dsp_cache" \ | |
| "action_selector_settings.txt" \ | |
| "free_keybindings_20250302_160606.txt" \ | |
| "paketti.sublime-workspace" \ | |
| "paketti.sublime-project" | |
| - name: Upload Release Asset | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: ${{ env.FILENAME }} | |
| asset_name: ${{ env.FILENAME }} | |
| asset_content_type: application/octet-stream | |
| # ── Job 3: Legacy package (API 5 – Renoise 3.1.x) ───────────────── | |
| package-api5: | |
| needs: create-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v2 | |
| with: | |
| submodules: false | |
| # Comment out modules that require platform binaries or API 6+ boot-time calls | |
| - name: Comment out incompatible modules | |
| run: | | |
| sed -i '/require\s*["'\'']PakettieSpeak["'\'']/s/^/-- /' main.lua | |
| sed -i '/require\s*["'\'']PakettiBeatDetect["'\'']/s/^/-- /' main.lua | |
| # Comment out function that references API 6+ features at load time | |
| - name: Comment out loadPaleGreenTheme function | |
| run: | | |
| sed -i '/function update_loadPaleGreenTheme_preferences()/,/^end/s/^/-- /' Paketti0G01_Loader.lua | |
| # Remove style attributes from ViewBuilder constructors (API 5 doesn't support style). | |
| # Runtime .style assignments are already safe — they use pakettiSetViewStyle() helper | |
| # which is a no-op on API < 6, so the sed doesn't need to touch them. | |
| - name: Remove style attributes from ViewBuilder constructors | |
| run: | | |
| # Step 1: Delete whole lines that are ONLY a style attribute (double-quoted) | |
| find . -type f -name "*.lua" -exec sed -i '/^\s*style\s*=\s*"[^"]*"\s*,\?\s*$/d' {} + | |
| # Step 2: Remove inline style with leading comma: {text="x", style="strong"} | |
| find . -type f -name "*.lua" -exec sed -i 's/,\s*style\s*=\s*"[^"]*"//g' {} + | |
| # Step 3: Remove inline style with trailing comma: {style="strong", text="x"} | |
| find . -type f -name "*.lua" -exec sed -i 's/style\s*=\s*"[^"]*"\s*,\s*//g' {} + | |
| # Step 4: Same three passes for single-quoted values (e.g. style='panel') | |
| find . -type f -name "*.lua" -exec sed -i "/^\s*style\s*=\s*'[^']*'\s*,\?\s*$/d" {} + | |
| find . -type f -name "*.lua" -exec sed -i "s/,\s*style\s*=\s*'[^']*'//g" {} + | |
| find . -type f -name "*.lua" -exec sed -i "s/style\s*=\s*'[^']*'\s*,\s*//g" {} + | |
| # Change manifest for side-by-side installation with the API 6 build. | |
| # Different Id allows both versions to coexist in Renoise's Tools folder. | |
| - name: Set manifest to API 5 (Paketti 3.1) | |
| run: | | |
| sed -i 's/<ApiVersion>6<\/ApiVersion>/<ApiVersion>5<\/ApiVersion>/g' manifest.xml | |
| sed -i 's/<Id>org.lackluster.Paketti<\/Id>/<Id>org.lackluster.Paketti31<\/Id>/g' manifest.xml | |
| sed -i 's/<Name>Paketti<\/Name>/<Name>Paketti 3.1<\/Name>/g' manifest.xml | |
| sed -i 's/<Version>3.54<\/Version>/<Version>3.1<\/Version>/g' manifest.xml | |
| # Replace v33/v34 XRNI presets with v31-compatible versions. | |
| # The Lua code routes via pakettiGetVersionedPresetPath() at runtime, | |
| # but overwriting the originals is belt-and-suspenders insurance for any | |
| # code path that references Presets/ directly. | |
| - name: Replace instrument presets with v31-compatible versions | |
| run: | | |
| echo "Replacing Presets/ instruments with v31 versions:" | |
| for f in Presets/v31/*.xrni; do | |
| base=$(basename "$f") | |
| echo " $base" | |
| cp "$f" "Presets/$base" | |
| done | |
| # Verify no syntax errors were introduced by the sed transformations | |
| - name: Syntax check all Lua files | |
| run: | | |
| sudo apt-get update -qq && sudo apt-get install -y -qq lua5.1 > /dev/null 2>&1 | |
| errors=0 | |
| while IFS= read -r -d '' f; do | |
| if ! luac5.1 -p "$f" 2>/dev/null; then | |
| echo "::error file=$f::Syntax error in $f" | |
| luac5.1 -p "$f" 2>&1 || true | |
| errors=$((errors + 1)) | |
| fi | |
| done < <(find . -name "*.lua" -not -path "./.git/*" -print0) | |
| if [ $errors -gt 0 ]; then | |
| echo "::error::$errors Lua file(s) have syntax errors after API5 transformations" | |
| exit 1 | |
| fi | |
| echo "All Lua files pass syntax check" | |
| - name: Set filename | |
| run: | | |
| echo "FILENAME=org.lackluster.Paketti31_V3.1_Renoise3.1_${{ needs.create-release.outputs.tag_name }}.xrnx" >> $GITHUB_ENV | |
| - name: Zip XRNX Package (API 5) | |
| run: | | |
| zip -r "${{ env.FILENAME }}" . \ | |
| -x "*.git*" "*.github*" "docs/*" ".spine/*" \ | |
| "preferences.xml" \ | |
| "preferencesDynamicView.xml" \ | |
| "preferences_deviceLoaders.xml" \ | |
| "preferences_midigen.xml" \ | |
| "preferences_pluginLoaders.xml" \ | |
| "definitions/*" \ | |
| "manual/*" \ | |
| "Presets/v31/*" \ | |
| "Presets/backup_v33_v34/*" \ | |
| ".dsp_cache" \ | |
| "action_selector_settings.txt" \ | |
| "free_keybindings_20250302_160606.txt" \ | |
| "paketti.sublime-workspace" \ | |
| "paketti.sublime-project" | |
| - name: Upload Release Asset (API 5) | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: ${{ env.FILENAME }} | |
| asset_name: ${{ env.FILENAME }} | |
| asset_content_type: application/octet-stream | |
| # ── Job 4: Spine + Architecture — Paketti's anatomy, coverage & shape ── | |
| # Runs Paketti's own registration code under a mocked Renoise (.spine/harness.lua) | |
| # for loop-expanded truth, and archof for the repo's architecture (radial / compact | |
| # images + the worktree ARCHITECTURE.md). All written to docs/ and attached to the | |
| # same release. No commit-back, no bot. (docs/ + .spine/ are excluded from the .xrnx.) | |
| package-spine: | |
| needs: create-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v2 | |
| with: | |
| submodules: false | |
| - name: Install LuaJIT + Graphviz | |
| run: sudo apt-get update -qq && sudo apt-get install -y -qq luajit graphviz | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Generate SPINE-TRUE.md + COVERAGE.md from the running code | |
| run: python3 .spine/build.py "$GITHUB_WORKSPACE" | |
| - name: Generate FEATURE-MAP.md + MIDI-GAPS.md (non-technical feature view) | |
| run: python3 .spine/features.py "$GITHUB_WORKSPACE/.spine/features.json" "$GITHUB_WORKSPACE" | |
| - name: Generate architecture understandings (radial / compact / worktree) | |
| run: | | |
| chmod +x .spine/archof | |
| mkdir -p docs | |
| # worktree understanding (text) → docs/ARCHITECTURE.md | |
| .spine/archof "$GITHUB_WORKSPACE" --write | |
| mv -f ARCHITECTURE.md docs/ARCHITECTURE.md | |
| # radial + compact images → docs/, normalized slug-independent names | |
| .spine/archof "$GITHUB_WORKSPACE" --images docs | |
| mv -f docs/*-architecture-radial.png docs/architecture-radial.png 2>/dev/null || true | |
| mv -f docs/*-architecture-compact.png docs/architecture-compact.png 2>/dev/null || true | |
| ls -la docs/ | |
| - name: Upload SPINE-TRUE.md | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: docs/SPINE-TRUE.md | |
| asset_name: SPINE-TRUE.md | |
| asset_content_type: text/markdown | |
| - name: Upload COVERAGE.md | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: docs/COVERAGE.md | |
| asset_name: COVERAGE.md | |
| asset_content_type: text/markdown | |
| - name: Upload ARCHITECTURE.md (worktree understanding) | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: docs/ARCHITECTURE.md | |
| asset_name: ARCHITECTURE.md | |
| asset_content_type: text/markdown | |
| - name: Upload architecture-radial.png | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: docs/architecture-radial.png | |
| asset_name: architecture-radial.png | |
| asset_content_type: image/png | |
| - name: Upload architecture-compact.png | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: docs/architecture-compact.png | |
| asset_name: architecture-compact.png | |
| asset_content_type: image/png | |
| # Refresh the docs/ TEXT understandings in the repo on every release, using the | |
| # releaser's existing identity (not a new bot). [skip ci] => no trigger loop. | |
| # The PNGs are NOT committed — release-assets-only — so the repo never bloats | |
| # with regenerated binaries (and docs/+.spine/ are already kept out of the .xrnx). | |
| - name: Commit refreshed docs (text only) back to master | |
| # Race-proof + non-fatal: concurrent release runs (or a human push) move | |
| # master while this runs, so a plain push gets "! [rejected] (fetch first)". | |
| # We retry by re-basing our generated docs onto the latest master (reset | |
| # --soft, last-writer-wins — the docs are regenerated, so overwriting is | |
| # correct and conflict-free). This step must NEVER fail the build: the | |
| # release assets are already uploaded; the in-repo refresh is cosmetic. | |
| run: | | |
| git config user.name "GitHub Action" | |
| git config user.email "action@github.com" | |
| git add docs/SPINE-TRUE.md docs/COVERAGE.md docs/ARCHITECTURE.md docs/FEATURE-MAP.md docs/MIDI-GAPS.md | |
| if git diff --cached --quiet; then | |
| echo "docs unchanged — nothing to commit" | |
| exit 0 | |
| fi | |
| git commit -m "docs: refresh spine + coverage + architecture [skip ci]" | |
| for attempt in 1 2 3 4 5; do | |
| if git push origin HEAD:master; then | |
| echo "docs pushed (attempt $attempt)"; exit 0 | |
| fi | |
| echo "push rejected (attempt $attempt) — rebasing docs onto latest master" | |
| git fetch origin master || true | |
| git reset --soft origin/master | |
| if git diff --cached --quiet; then | |
| echo "docs already current on remote — nothing to push"; exit 0 | |
| fi | |
| git commit -m "docs: refresh spine + coverage + architecture [skip ci]" | |
| sleep 3 | |
| done | |
| echo "could not push docs after retries — non-fatal, next run will refresh" | |
| exit 0 |