chore: sync development to main#2774
Conversation
Two gaps since we swapped @semantic-release/release-notes-generator for the custom AI-backed tool: - No top-level "# [X.Y.Z](compare-url) (YYYY-MM-DD)" header. Now prepended in all emit paths (model, fallback, empty). - No "New Contributors" section. Now fetched from GitHub's POST /releases/generate-notes endpoint and appended if present.
…lows
Five workflows under a unified Fider - * naming scheme that operate the
features.maintainerr.info Fider instance from CI. Tags + comments only —
no auto-closes from the triage path. Maintainers always have the final
call on possibly-* posts; the stale sweep is the one place the bot will
auto-decline (after a 30-day warning + grace window).
## Workflows
fider-triage.yml Fider - Triage cron daily 06:00 UTC
fider-re-evaluate.yml Fider - Re-evaluate cron monthly + manual
fider-pre-existing-scan.yml Fider - Pre-existing scan manual only
fider-stale.yml Fider - Stale sweep cron weekly Sunday
fider-invite-codeowners.yml Fider - Invite CODEOWNERS cron weekly Sunday
## Tags applied by the bot
triage-checked every processed post (idempotency gate)
possibly-completed a recent merged PR clearly delivers the request
possibly-duplicate this post duplicates an earlier open post
possibly-pre-existing feature already shipped before the post was filed
(lower confidence than possibly-completed)
stale open >1.5 years with <3 votes; warned for review
## Triage pipeline (per post)
1. Skip if already triage-checked, unless FORCE_REEVAL=true
2. Extract keywords from title + first 400 chars of description, drop
stopwords, keep top 4
3. Search merged PRs one keyword at a time via gh pr list, union and
dedupe (AND'ing keywords misses real matches)
4. Filter candidates: feat/fix/feature prefix only, mergedAt > createdAt
5. Ask the model (openai/gpt-4.1-mini) with a strict JSON rubric that
requires a verbatim quote from a candidate PR; verify the quote
actually appears in what we sent (defence against hallucination)
6. If status=completed + confidence=high: tag possibly-completed and
upsert a comment citing the evidence URL and verbatim quote (PUT
to update on re-eval if a better PR is now cited)
7. (CHECK_PRE_EXISTING=true) Re-run the search with mergedAt < createdAt
and a stricter rubric to surface "user didn't know feature exists"
8. Compare against other open posts (Jaccard >= 0.4 on keywords) for
duplicate detection; the cited original_number must be in the
candidate list AND the quote must appear in its title/description
## Stale sweep (two-phase)
Phase 1: post older than 1.5 years with <3 votes and no exempt tag →
apply 'stale' tag, post a "still relevant?" warn comment
carrying the same cc:@maintainers prefix as triage comments
Phase 2: 30 days later, if no engagement (any comment newer than the
warn) → PUT /api/v1/posts/{n}/status to declined with
explanation
Exempt tags: never-stale, planned, started, bug, enhancement,
possibly-completed
## Invite-CODEOWNERS sweep
Weekly: parse .github/CODEOWNERS, look up each user's public GitHub
email, skip those already Collaborator/Administrator on Fider, send
remaining recipients a single POST /api/v1/invitations/send. Users
with private email visibility are logged and skipped (no automatic
fallback — invite manually if needed).
## Safety guards (triage)
- Per-comment-type hidden HTML markers prevent double-commenting on
re-runs (completed / duplicate / pre-existing each independent);
upsertBotComment edits in place if the body changed
- 5-second throttle between model calls (free-tier 15 req/min cap)
- Per-call retry on 429/5xx: 60s/120s/240s backoff, honour Retry-After
but cap at 5 minutes — anything beyond (e.g. 23-hour daily quota
reset) becomes a hard failure so the run aborts cleanly instead of
sleeping inside CI
- Abort run after 3 consecutive model failures (~15s on quota-exhausted
days, ~21min worst case during real outages) — remaining posts get
picked up on the next scheduled run
- Rate-limit headers logged via an explicit allowlist; trace IDs and
infra identifiers are deliberately NOT logged
- CODEOWNERS guard on workflow_dispatch
- models: read permission only; no actions write, repo write, PR write
## Maintainer @mentions on every bot comment
Resolves a "cc:" prefix once per run from GET /api/v1/users (works for
Collaborator OR Administrator per docs.fider.io/api/users) or falls
back to FIDER_MENTION_USERS_FALLBACK env var. The bot's own user is
auto-excluded via FIDER_BOT_USERNAME (no /me endpoint exists in
Fider's public API). If neither source yields anyone, the comment
posts without the cc line.
## Shared helpers
tools/fider-shared.mjs provides createFider, postHasTag,
buildMentionToken, and buildMentionPrefix to all four entry-point
scripts so the API client, role filtering, and cc-prefix logic stay
in one place.
## Bot identity
GitHub user: maintainerr-fider-bot (Read collaborator on the repo)
Fider role: Collaborator (with one-time Administrator promotion
to create the five private tags)
Secret: FIDER_API_KEY (personal access token)
The first live stale-sweep run failed with 404 on tag application
because the 'stale' tag didn't exist on Fider yet — the script never
called the equivalent of fider-triage's ensureTags(). Both scripts
now use a single shared helper:
- New export ensureTags({ fider, log, dryRun, host, tags }) in
tools/fider-shared.mjs that idempotently creates a list of tags
and converts the 403-on-Collaborator failure into the same
promote-then-demote instructional error message both scripts
used to inline.
- fider-stale.mjs now calls it for the 'stale' tag (color e74c3c).
- fider-triage.mjs's local ensureTags shrinks from 36 lines to a
10-line invocation passing its four tag definitions.
Net: -23 lines despite adding tag creation to fider-stale.
The cc: prefix was cosmetic — Fider doesn't send notifications on @mentions, so maintainers never saw the bot's output until they opened the post directly. Two visible bugs in the live runs (bot included itself as @maintainerr-fider-bot, ydkmlt84 listed twice because of two Fider accounts under the same display name) were also confusing. Replace it with a Discord webhook integration. ## What changed - New notifyDiscord helper in fider-shared.mjs. Posts a coloured embed to a webhook URL on each meaningful bot action: possibly-completed, possibly-pre-existing, possibly-duplicate, stale-warned, and stale-declined. Embed contains the post title (clickable), the cited PR / quote / vote count, and a colour matching the Fider tag. - Optional DISCORD_PING_ROLE_ID — when set, the message also includes <@&ROLE_ID> in the content field so a Discord role gets pinged (embeds alone never trigger Discord notifications). allowed_mentions is scoped to that single role so it can never accidentally @everyone. - All buildMentionPrefix / withMentionPrefix / FIDER_BOT_USERNAME / FIDER_MENTION_USERS_FALLBACK code removed from triage and stale scripts. Comments now post without a cc line. - buildMentionPrefix and buildMentionToken removed from tools/fider-shared.mjs (they were the only consumers). ## Workflow plumbing All four runtime workflows (fider-triage, fider-re-evaluate, fider-pre-existing-scan, fider-stale) pass two new env vars: DISCORD_FIDER_BOT_WEBHOOK ← repo secret DISCORD_PING_ROLE_ID ← repo secret (treated as sensitive too) Both default to empty: webhook empty = no notifications, role ID empty = embed-only with no @-ping. The bot's Fider work always happens; Discord is best-effort and never throws on failure. ## Setup required by maintainers 1. Create a Discord webhook in the target channel (Server Settings → Integrations → Webhooks → New Webhook), copy the URL, store as repo secret DISCORD_FIDER_BOT_WEBHOOK. 2. (Optional) Create or pick a "maintainers" role in the Discord server, copy its numeric snowflake ID (Developer Mode → right-click role → Copy ID), store as repo secret DISCORD_PING_ROLE_ID. Members of that role get pinged on every bot action.
…ons (#2766) * fix: stop cross-rule contamination for same-titled automatic collections Two automatic rule groups with the same title end up linked to the same media server collection because checkAutomaticMediaServerLink relinks by name. The rule executor's manual-import path then imported the sibling rule's items as manual into the wrong rule, subjecting them to the wrong deleteAfterDays. Whichever rule emptied first would also delete the shared media server collection in removeFromCollection / checkAutomaticMediaServerLink, taking the sibling rule's items with it. Skip the manual import for items already rule-owned by a sibling collection sharing the same mediaServerId, and guard the two empty- collection deletes against shared links by unlinking locally instead. Refs #2765 * fix: resync rule-owned items + close sibling-lookup gaps Adds three corrections on top of the same-title automatic collection contamination fix: - Partial drift: checkAutomaticMediaServerLink now diffs server children against local rule-owned items for shared automatic collections (not just empty ones) and pushes any missing back via addBatchToCollection. Fixes the "Plex shows 1 of 5 items, Maintainerr DB shows 5" case where the rule executor's local-DB-only delta can't recover server drift. - Sibling lookup error path: getSiblingRuleOwnedMediaServerIds no longer swallows DB errors into an empty Set. The rule executor catches the throw and skips manual child import, since a silent empty would let sibling-owned children be imported as manual into the wrong rule. - Cross-type sharing: isMediaServerCollectionShared and getSiblingRuleOwnedMediaServerIds now filter siblings by manualCollection so a manual collection sharing a mediaServerId can no longer be treated as a sibling automatic rule group.
…lease - Adds a "Behavioral fixes worth reviewing" section that scans fix: commits and surfaces the ones touching user-visible surfaces (UI, settings, notifications, collections, rule executor, controllers, README) so behavior-changing fixes don't slip past the drift report. Each entry lists the touched files for cheap triage. - workflow_dispatch base_ref is now a small choice dropdown (latest / origin/main / origin/development) defaulting to 'latest', which the resolve step expands to the most recent GitHub release tag at run time. No per-version maintenance burden.
When two automatic rule groups share a media server collection (same title in the same library), each emits its own CollectionMedia_Added / _Removed for the same item, so the user sees N identical notifications for what is one user-visible change. Dedupe these within a single rule-executor batch (one processQueue() pass), keyed on (event, collectionName, mediaServerId). Notifications across separate batches (e.g. scheduled runs at different times) are never collapsed — those are genuinely distinct events. Manual / user-triggered notifications are unaffected because dedupe lives in the @onevent handlers, not in handleNotification itself. Reuses the existing RuleHandlerQueue_StatusUpdated event for batch lifecycle so no new contracts surface is added. Adds a defensive emitStatusUpdate() to processQueue's outer finally so listeners can observe the processingQueue=true→false transition reliably (previously no event fired on drain). Verified safe under stop/abort: the same finally path runs and clears dedupe state.
|
|
|
See the earlier blocker comment on this PR for specifics. |
📚 Docs drift reportComparing Rule glossary parity
Glossary is in sync with the code. New migrations on this branchNo new migrations. Rule constantsNo changes to Public contracts (
|
|
🚀
|
|
🎉 This PR is included in version 3.9.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Promotes
developmenttomainfor release. Squash-merge when approved; release automation continues on approval.Changes