4 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 f4685eeafe one plugins directory, because the second one was symlinks
`officer-dev/plugins/` is gone. it was created to hold every repo in the org side
by side, and by today it held 22 symlinks into platform/plugins, one duplicate
clone of a plugin that is installed here, and a README describing a state that
had already been fixed.

it could not have been more than that. a plugin cannot live outside the platform:
bun resolves a symlink to its real path, so from there `@@/sidecar/create-proxy`
and every relative import fail. tested both, plus `--preserve-symlinks`, which
bun 1.3.14 accepts and ignores.

WHICH MATTERS BEYOND TIDYING, because marketplace-sync leaned on the distinction.
its comment said the second directory was "where a release is actually made", so
publishing from it would advertise released versions rather than installed ones.
that was never true — symlinks meant both paths read the same working copy. the
bug it describes (21 plugins tagged, the store still showing 1.0.0) was never
addressed by the thing built to address it.

the honest fix is in the comment now: each plugin directory IS a git repo with
its own tags, so the released manifest can be read out of the tag —
`git show <tag>:manifest.ts` — without a second checkout. not written yet, and
said plainly rather than left implied.

finances is deleted, at the owner's request: not installed, no pm2 entry, no
ecosystem entry, no plugin_installs row, no tables. both clones were clean and
pushed. the gitea repo is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-28 22:23:59 +01:00
pastilhasandClaude Opus 5 c14b686782 pm2 save when the process list changes, and a check that does not lie about symlinks
the owner asked whether anything runs `pm2 save` when a sidecar is added or
removed. nothing did.

PM2 resurrects from ~/.pm2/dump.pm2, written only by `pm2 save`, so the dump was
whatever a human last happened to save. today it was FIVE DAYS old: 13 processes
saved against 26 running. a reboot would have silently lost every sidecar
installed since — bitcoin-wallet, desktop, gitea, invoices, iris, jellyfin,
minesweeper, music, photos, soulseek, transcribe, transmission, vault. thirteen
features gone, machine healthy, nothing saying why.

saved after start AND after delete. removal matters as much: a dump that keeps
resurrecting a deleted sidecar brings it back after a reboot as a crash loop for
code no longer on the machine. a RESTART does not save — the same processes are
running and the dump is already right.

best effort and never fatal: a failed save leaves the estate correct and only
the dump stale, which is where things already were. failing an install over
bookkeeping would be the worse trade.

separately, plugins:check filtered directories on the DIRENT's isDirectory(),
which answers false for a symlink to a directory. platform/plugins is symlinked
now, so the check would have reported "0 plugin repositories, all well-formed" —
a green check over nothing, which is worse than a red one. it stats the path
instead, the way discover.ts already does.

dump saved once by hand as well, since five days of drift was already there:
26 saved, 26 running, no difference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 07:52:46 +00:00
pastilhas 1c28a48299 the check can look at either plugins directory, and knows what is not a plugin
there are two now. `platform/plugins/` is what this machine has INSTALLED;
`officer-dev/plugins/` is every repo in the org cloned side by side, which is
where a plugin is worked on from now on. they hold different sets, so the check
has to be pointable rather than assuming the one it sits next to.

and it skips a repo with no manifest.ts instead of reporting it broken.
`av-club` is the case — a standalone media service in the plugins org, meant to
become a plugin in time. checking it against a plugin's conventions would flag
its branch name as a defect for ever, and a check that cries wolf is one people
stop reading. presence is the declaration here exactly as in discover.ts: the
manifest is what makes something a plugin, so its absence is what makes
something not one yet.
2026-08-27 07:09:06 +00:00
pastilhasandClaude Opus 5 d381140d54 plugins say what they are for, and their checkouts stop lying about themselves
two things, and the second is why the first was painful.

TAGS. the marketplace is going to be browsed rather than scrolled: tags on the
left, the plugins carrying one on the right. a plugin declares its own in the
manifest and the catalogue caches it on sync, exactly as it already does for
label and summary — the alternative is editing a table every time a plugin
ships, and a plugin that cannot say what it is.

the vocabulary is CLOSED (PLUGIN_TAGS) because free text becomes homelab,
home-lab, Home Lab and selfhosted inside a month, and a filter list of synonyms
is worse than no filter. an unknown tag is refused at discovery BY NAME, so the
author is told which tag rather than which array index.

all 21 are tagged, each with a one-line note saying why, so a later disagreement
is with a stated reason rather than a mystery. every tag has at least three
plugins behind it — no filter opens onto an empty page.

PLUGINS:CHECK. getting one commit into 21 repositories failed seven times and
none of the reasons were about the commit: six remotes were HTTPS and cannot
authenticate unattended, three checkouts were behind their remote, one URL had
no .git suffix so the catalogue published a clone URL unlike every other, and
offscale's fetch refspec named a branch that does not exist.

that last one is why this is a script and not a habit. every command reported on
a phantom — `git push` said "Everything up-to-date" while the branch looked
diverged — and the repository was fine the whole time. twenty minutes spent in a
repo with nothing wrong with it.

none of it is visible until you try to push, which is the worst moment to learn
it: mid-task, across 21 repositories, and every failure looks like it is about
your change.

so it checks the facts with one right answer — remote URL, fetch refspec,
branch, upstream, dirty tree, ahead/behind — and exits non-zero. `--fix` is a
separate flag so running the check can never be what changes your tree, and
divergence is never auto-repaired because it needs a decision about content.

verified by breaking each case on purpose and watching it catch them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 06:55:03 +00:00