--- name: plugin-git-install-committed-artifact description: "Use when shipping or landing an omp/pi plugin (omp plugin install/upgrade git): install validation fails on a missing build artifact, a stale bun git-mirror cache is reused, the live bundle needs proof it is the merged build, or upgrade says \"not installed\"." --- # Plugin install from git: committed artifact + fresh mirror `omp plugin install git:` (and `pi install git:`) **clone the repo and validate the manifest's declared extension entry WITHOUT running a build step**. Two failure classes seen live (dank/dankvoice, 2026-10-19): ## 1. Declared entry missing from the clone Symptom: `Plugin extension validation failed: ./dist/index.js: declared extension entry not found on disk`. Cause: build output (`dist/`) is gitignored; the manifest points at it; the clone has only `src/`. Fix — ship the artifact: - Remove `dist/` from `.gitignore`, `bun run build`, commit `dist/`. - Prove build determinism first: build twice, `cmp` the outputs (bun esm builds are byte-reproducible without sourcemaps). - Pin the contract in a small bats suite so it can't rot: 1. the manifest's declared entry is tracked in git (`git ls-files -- "${entry#./}"` non-empty — this is the installer's exact contract, not a config assertion); 2. committed bytes equal a fresh build (`bun run build` then `git diff --name-only -- dist` is empty → red on drift). ## 2. Stale bun git-mirror cache Symptom: fix pushed, retry **still** fails identically, and the retry is suspiciously fast (seconds). Cause: bun keeps bare mirrors in `~/.bun/install/cache/.git` and may reuse them at an old commit — even after the fix is pushed and the default branch is correct. Diagnose + fix: ```bash find ~/.bun/install/cache -maxdepth 2 -iname "**" git --git-dir ~/.bun/install/cache/.git log --oneline -3 --all # stale HEAD? rm -rf ~/.bun/install/cache/.git # it is a cache; safe ``` Prove the fetch path independently first (also validates credentials for private Gitea repos): ```bash GIT_TERMINAL_PROMPT=0 git clone -q /tmp/x && git -C /tmp/x log --oneline -1 && ls /tmp/x/dist ``` ## Order of investigation (don't skip) 1. Gitea default branch correct? (`GET /api/v1/repos//` with token — private repos 404/None unauthenticated.) 2. Plain unattended clone contains the entry? If yes, the installer's view is cached → purge mirror. 3. Only then suspect the installer itself (validation code lives in the minified bundle; `grep -oE '.{600}' "$bunfile"` on `readlink -f $(which omp)` shows the exact check). ## Landing a merged PR on the live box After the merge and the `--ff-only` on the dev checkout (`ls -ld node_modules` after landing anything that could have touched it): - **`omp plugin upgrade ` takes the PACKAGE name, not the install alias.** `omp plugin upgrade dankvoice` fails `dankvoice is not installed` while the live copy sits in `~/.omp/plugins/node_modules/omp-dankvoice/`. Read the directory name (or `package.json` `"name"`) and upgrade that. - Then close the chain of custody with one command — the deployed bytes are the merged bytes: ```bash cmp ~/Projects//dist/index.js ~/.omp/plugins/node_modules//dist/index.js ``` If only docs/tests changed, `dist/` is untouched and this should already match; if `src/` changed, the plugin must be upgraded first or this fails with `char ` — which is a real finding (stale live bundle), not a test bug. - The session that did the merge keeps the OLD bundle in memory: it is still running pre-merge code. Never conclude "the change does not work" from the merging session. ## After install — proof without a TUI - `omp plugin doctor` → per-plugin ✔ line. - Audible/behavioral chain when TUI input is unusable (update-banner modal freezes pty submits): run `omp --mode=rpc --no-session` with the feature env armed (dankvoice: `DANKVOICE_AUTO=1`, gate is print/json-only) and watch for objective side effects, e.g. PipeWire: `pw-dump | grep -q "say-.wav"` proves synth+playback end-to-end. - When pty submits DO work (no modal), the strongest instrument is a stub player plus a clip count, not ears or pixels: `DANKVOICE_PLAYER=/tmp/stub.sh` where the stub appends `$1` to a log and exits 0. Launch the DEPLOYED bundle by path (`omp --no-extensions --no-session -e ~/.omp/plugins/node_modules//dist/index.js`) in a neutral cwd with `DANKVOICE_CONFIG` pointed at a scratch file so the operator's settings are untouched. - **Count clips per narration EVENT, not per turn.** One turn can fire several narrations (dankvoice: `message_end` carries prose+thinking, `agent_end` the settled answer; the dedupe only skips byte-identical text, so thinking-on means both speak). A "too many clips" result is usually correct behaviour — check the harness transcript for the turn count before believing a bug. The player deletes each temp wav, so the log line count is the only surviving evidence: park it in `~/.AGENTTRASH/-/` if it becomes evidence for an issue.