Skip to content

PakettiMCP: non-wedging reload + watchdog self-heal + structured eval… #1520

PakettiMCP: non-wedging reload + watchdog self-heal + structured eval…

PakettiMCP: non-wedging reload + watchdog self-heal + structured eval… #1520

Workflow file for this run

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