Skip to content

feat: maintainer toolset β€” scheduled triage sweep + daily Discord scorecard#950

Open
tombeckenham wants to merge 6 commits into
mainfrom
maintainer-toolset
Open

feat: maintainer toolset β€” scheduled triage sweep + daily Discord scorecard#950
tombeckenham wants to merge 6 commits into
mainfrom
maintainer-toolset

Conversation

@tombeckenham

@tombeckenham tombeckenham commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Adds a maintainer toolset β€” two scheduled workflows backed by a unit-tested TypeScript toolkit in scripts/maintainer/, configured via .github/maintainers.json:

  • Triage sweep (maintainer-sweep.yml, every 3h): assigns open PRs/issues to maintainers by least-loaded rotation with caps, posts a one-time ack comment with a pre-review checklist (CI, conflicts, changeset, E2E), asks for a reproduction on bug reports lacking one, and keeps waiting-on / ready-to-merge / needs-repro / has-pr labels in sync. Suspected drive-by/bounty PRs are flagged for human judgment, never acted on.
  • Daily Discord scorecard (maintainer-scorecard.yml): posts a to-do digest β€” SLA breaches, ready-to-merge PRs, per-maintainer queues, new/stale items, unanswered discussions, and response-time stats. Untrusted item titles are markdown-escaped before rendering.

Everything is deterministic and stateless: metrics are recomputed from GitHub timelines each run, PR code is never checked out or executed (no pull_request_target), and workflows run with minimal permissions and pinned action SHAs. Both workflows support manual dry-run dispatch.

AI-assisted triage (issue summaries, smarter repro detection, duplicate detection) is intentionally not in this PR β€” it will come as a follow-up layered on top of this deterministic base.

Before enabling: create the DISCORD_MAINTAINER_WEBHOOK secret and add Discord user IDs to maintainers.json. See scripts/maintainer/README.md.

βœ… Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

πŸš€ Release Impact

  • This change is docs/CI/dev-only (no release).

πŸ€– Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added scheduled and manually-triggered maintainer automation workflows for sweeping PRs/issues and generating daily scorecards.
    • Sweep now assigns and applies labels, and posts idempotent acknowledgement/reproduction requests when needed.
    • Scorecards compute SLA/workload metrics and publish Discord embeds (delivered in chunks) with a dry-run preview option.
  • Documentation
    • Added maintainer tool documentation covering configuration, local runs, and workflow behavior.
  • Tests
    • Expanded test coverage for triage/classification logic, routing, scorecard metrics/rendering, and sweep planning constraints.

…recard

Adds two stateless, API-only scheduled workflows driven by a fixture-tested
TypeScript toolkit in scripts/maintainer/:

- maintainer-sweep.yml (every 3h): routes and assigns open PRs/issues to
  maintainers via .github/maintainers.json area globs with least-loaded
  rotation and per-maintainer caps, posts a one-time ack comment with a
  deterministic pre-review checklist (CI, conflicts, changeset, E2E),
  requests reproductions on bug reports lacking one, and reconciles
  waiting-on / ready-to-merge / needs-repro / has-pr labels. Suspected
  drive-by/bounty PRs (new account + trivial diff + no linked issue) are
  left untouched for human judgment.

- maintainer-scorecard.yml (daily): posts a to-do digest to Discord β€”
  SLA breaches (24h first response / 48h follow-up), ready-to-merge PRs,
  per-maintainer queues with fresh contributor replies, new/flagged/stale
  items, unanswered discussions, same-author PR clusters, and stats
  (open deltas, first-response median/p90, merges, pending changesets).

All metrics are recomputed from GitHub timelines each run (no stored
state); idempotency comes from HTML markers in bot comments plus visible
assignment/label state. PR code is never checked out or executed β€” no
pull_request_target. Unit tests wire into CI via the root test:maintainer
nx target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 268d5b86-3e00-4270-991e-033f95e5e23f

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between fac410b and 553c6d3.

πŸ“’ Files selected for processing (12)
  • .github/workflows/maintainer-scorecard.yml
  • .github/workflows/maintainer-sweep.yml
  • scripts/maintainer/README.md
  • scripts/maintainer/actions.ts
  • scripts/maintainer/classify.test.ts
  • scripts/maintainer/classify.ts
  • scripts/maintainer/config.ts
  • scripts/maintainer/discord.ts
  • scripts/maintainer/github.ts
  • scripts/maintainer/metrics.ts
  • scripts/maintainer/sweep.ts
  • scripts/maintainer/types.ts
 _____________________________________________________________________
< Not saying it's bad... but the compiler just asked for a union rep. >
 ---------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
πŸ“ Walkthrough

Walkthrough

Introduces a maintainer automation toolset that collects GitHub repository data, classifies and routes work, plans or executes sweep mutations, computes scorecards, publishes Discord digests, and runs through scheduled or manual GitHub Actions workflows.

Changes

Maintainer automation

Layer / File(s) Summary
Contracts, configuration, and GitHub collection
scripts/maintainer/types.ts, scripts/maintainer/config.ts, scripts/maintainer/github.ts, scripts/maintainer/collect.ts, scripts/maintainer/env.ts
Defines normalized repository and triage types, validates maintainer policy, provides injectable GitHub GraphQL/REST access, and collects paginated repository snapshots.
Triage and deterministic routing
scripts/maintainer/classify.ts, scripts/maintainer/route.ts, scripts/maintainer/glob.ts, scripts/maintainer/fixtures.ts, scripts/maintainer/*test.ts
Classifies SLA, readiness, hygiene, spam, reproduction, and discussion states, then routes eligible PRs and issues by ownership, load, caps, and deterministic rotation.
Sweep planning and GitHub mutations
scripts/maintainer/sweep.ts, scripts/maintainer/actions.ts, scripts/maintainer/comments.ts, scripts/maintainer/sweep.test.ts
Plans bounded assignments, comments, reproduction requests, and managed-label reconciliation, with idempotency, dry-run handling, and REST execution.
Scorecard metrics and Discord delivery
scripts/maintainer/metrics.ts, scripts/maintainer/discord.ts, scripts/maintainer/scorecard.ts, scripts/maintainer/metrics.test.ts
Computes categorized metrics, renders and chunks Discord embeds, writes step summaries, and conditionally posts or logs scorecard output.
Repository integration
.github/maintainers.json, .github/workflows/*, package.json, scripts/maintainer/README.md
Adds maintainer policy, package commands, scheduled/manual workflows, permissions and secrets wiring, and maintainer tool documentation.

Estimated code review effort: 5 (Critical) | ~120 minutes

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check name Status Explanation
Title check βœ… Passed The title clearly summarizes the main change: a maintainer toolset with scheduled triage sweep and daily Discord scorecard.
Description check βœ… Passed The description matches the template with Changes, Checklist, and Release Impact sections filled out.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
πŸ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch maintainer-toolset
βš”οΈ Resolve merge conflicts
  • Resolve merge conflict in branch maintainer-toolset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

πŸš€ Changeset Version Preview

No changeset entries found. Merging this PR will not cause a version bump for any packages.

@nx-cloud

nx-cloud Bot commented Jul 16, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution β†— for commit fac410b

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... βœ… Succeeded 2m View β†—

☁️ Nx Cloud last updated this comment at 2026-07-20 04:11:44 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@950

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@950

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@950

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@950

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@950

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@950

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@950

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@950

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@950

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@950

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@950

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@950

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@950

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@950

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@950

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@950

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@950

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@950

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@950

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@950

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@950

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@950

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@950

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@950

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@950

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@950

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@950

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@950

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@950

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@950

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@950

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@950

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@950

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@950

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@950

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@950

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@950

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@950

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@950

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@950

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@950

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@950

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@950

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@950

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@950

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@950

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@950

commit: fac410b

tombeckenham and others added 3 commits July 16, 2026 14:16
…ation

The team doesn't split by code area, so drop the guessed area globs from
maintainers.json. Assignment is now pure least-loaded rotation; `areas`
stays supported as an optional per-maintainer routing priority.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Issue/PR titles are attacker-controlled and were interpolated verbatim
into Discord embed descriptions, so a title like
"[urgent: click](https://evil.com)" rendered as a masked link in the
maintainer channel. shortTitle() now backslash-escapes Discord markdown
metacharacters after truncation; the bot's own [#N](url) links are
built separately and still render. Also simplifies a redundant type
assertion in the GraphQL error check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tombeckenham
tombeckenham marked this pull request as ready for review July 16, 2026 06:10
@tombeckenham
tombeckenham requested a review from a team as a code owner July 16, 2026 06:10
@tombeckenham
tombeckenham requested a review from AlemTuzlak July 16, 2026 06:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/maintainer-scorecard.yml:
- Around line 30-31: Update permissions in
.github/workflows/maintainer-scorecard.yml at lines 30-31 to add issues: read,
pull-requests: read, and discussions: read alongside contents: read. Update
permissions in .github/workflows/maintainer-sweep.yml at lines 28-31 to add
discussions: read while preserving its existing issues: write and pull-requests:
write permissions.

In `@scripts/maintainer/classify.ts`:
- Around line 170-176: Update changesRequestedOutstanding to use the latest
CHANGES_REQUESTED review and compare only PR-author comments or commits
occurring after that review; stop relying on aggregate lastContributorActivityAt
and lastMaintainerResponseAt timestamps. Preserve the outstanding state when no
author activity follows the requesting review, and add regression coverage for
intervening third-party and maintainer comments.
- Around line 165-168: Update the readyToMerge calculation in
scripts/maintainer/classify.ts to require explicitly successful CI and
explicitly mergeable pull-request states, rather than only excluding failure and
conflict states. Preserve the existing approval and non-draft requirements, and
add negative tests covering pending CI and unknown mergeability.
- Around line 135-142: Update hasMarkerComment to consider a marker only when
the timeline event is an automation-generated comment, while preserving the
existing marker-body matching. Use the event’s existing author or automation
identity field to exclude contributor comments before checking
e.body.includes(marker).

In `@scripts/maintainer/collect.ts`:
- Around line 125-130: Update the snapshot collection logic around the GraphQL
connections files, closingIssuesReferences, timelineItems, and discussion
comments so capped results are not treated as complete data. Propagate each
connection’s pagination/completeness metadata through the collected snapshot,
and ensure downstream triage automation either fetches all pages or suppresses
decisions when any required connection is truncated.

In `@scripts/maintainer/config.ts`:
- Around line 18-20: Update the validation guarding repo in the configuration
flow to require exactly one slash separating two non-empty owner and name
components, rejecting values such as owner/, /repo, and owner/repo/extra before
collectSnapshot receives them.

In `@scripts/maintainer/discord.ts`:
- Around line 201-209: Bound the webhook request in the chunkEmbeds loop by
adding an AbortController-based timeout to the fetchImpl call, passing its
signal and clearing the timer when the request settles. Handle timeout or abort
failures explicitly so the scheduled workflow exits through the intended failure
path, and add coverage for the stalled-request case.
- Around line 201-209: Update the webhook payload in the embed-sending loop to
place maintainer mentions in the top-level content field rather than only inside
embed descriptions. Use the existing mention-related symbols to build that
content for alerting messages, and set allowed_mentions.users to exactly the
corresponding maintainer IDs while preserving the existing embed chunking and
POST behavior.
- Around line 116-123: Update the embed construction around flaggedLines and the
β€œNew in the last 24h” title to place flagged entries before newLines when
building the section, and include the flagged count in the title alongside the
new item count. Preserve the existing flagged-line formatting and section
truncation behavior.

In `@scripts/maintainer/github.ts`:
- Around line 15-19: Update GitHubClientOptions and the request paths in the
GitHub client to support an injectable timeout, using it to abort every GitHub
fetch when the deadline expires. Ensure the timeout applies consistently to both
API paths while preserving the existing fetchImpl and baseUrl behavior.

In `@scripts/maintainer/metrics.ts`:
- Around line 238-246: Update the assignment-coverage calculation around
assignablePRs and pctPRsAssigned to exclude PRs marked suspectedSpam from the
denominator, while preserving the existing draft exclusion and assigned-count
logic. In scripts/maintainer/README.md lines 5-9, clarify that assignment
applies only to eligible items and document the cap/flag exceptions.
- Around line 224-235: Update the recentlyClosed loop to exclude items whose
author matches the configured roster, using the same case-insensitive
rosterAuthor check already applied to open samples. Extend the existing
early-continue condition alongside authorIsBot and the weekAgo cutoff, while
preserving response state derivation for eligible items.
- Around line 126-131: Update the freshReplies filter to require an explicit
contributor-after-maintainer transition for both pull requests and issues,
rather than treating every item with waitingOn === 'maintainer' as fresh. Use
the existing freshContributorReply state where available and add equivalent
transition tracking to the issue data before filtering; preserve only items that
actually represent a new contributor reply.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6551ce8e-7d7d-43cd-a602-daf6e7ef787f

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5fcaf90 and 5169c28.

πŸ“’ Files selected for processing (24)
  • .github/maintainers.json
  • .github/workflows/maintainer-scorecard.yml
  • .github/workflows/maintainer-sweep.yml
  • package.json
  • scripts/maintainer/README.md
  • scripts/maintainer/actions.ts
  • scripts/maintainer/classify.test.ts
  • scripts/maintainer/classify.ts
  • scripts/maintainer/collect.ts
  • scripts/maintainer/comments.ts
  • scripts/maintainer/config.ts
  • scripts/maintainer/discord.ts
  • scripts/maintainer/env.ts
  • scripts/maintainer/fixtures.ts
  • scripts/maintainer/github.ts
  • scripts/maintainer/glob.ts
  • scripts/maintainer/metrics.test.ts
  • scripts/maintainer/metrics.ts
  • scripts/maintainer/route.test.ts
  • scripts/maintainer/route.ts
  • scripts/maintainer/scorecard.ts
  • scripts/maintainer/sweep.test.ts
  • scripts/maintainer/sweep.ts
  • scripts/maintainer/types.ts

Comment thread .github/workflows/maintainer-scorecard.yml
Comment thread scripts/maintainer/classify.ts
Comment thread scripts/maintainer/classify.ts Outdated
Comment on lines +165 to +168
const hasConflicts = pr.mergeable === 'CONFLICTING'
const ciFailing = pr.ciState === 'failure'
const approved = pr.reviewDecision === 'APPROVED'
const readyToMerge = approved && !hasConflicts && !ciFailing && !pr.isDraft

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

Require explicitly successful CI and mergeability before marking ready.

Line 168 treats pending/unknown CI and mergeability as ready because it only excludes known failure and conflict states. This can publish non-mergeable PRs in the ready queue.

Proposed fix
 const approved = pr.reviewDecision === 'APPROVED'
-const readyToMerge = approved && !hasConflicts && !ciFailing && !pr.isDraft
+const readyToMerge =
+  approved &&
+  pr.mergeable === 'MERGEABLE' &&
+  pr.ciState === 'success' &&
+  !pr.isDraft

Add negative tests for pending CI and unknown mergeability.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasConflicts = pr.mergeable === 'CONFLICTING'
const ciFailing = pr.ciState === 'failure'
const approved = pr.reviewDecision === 'APPROVED'
const readyToMerge = approved && !hasConflicts && !ciFailing && !pr.isDraft
const hasConflicts = pr.mergeable === 'CONFLICTING'
const ciFailing = pr.ciState === 'failure'
const approved = pr.reviewDecision === 'APPROVED'
const readyToMerge =
approved &&
pr.mergeable === 'MERGEABLE' &&
pr.ciState === 'success' &&
!pr.isDraft
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintainer/classify.ts` around lines 165 - 168, Update the
readyToMerge calculation in scripts/maintainer/classify.ts to require explicitly
successful CI and explicitly mergeable pull-request states, rather than only
excluding failure and conflict states. Preserve the existing approval and
non-draft requirements, and add negative tests covering pending CI and unknown
mergeability.

Comment on lines +170 to +176
// Outstanding changes-requested: the decision stands and the contributor
// hasn't commented or pushed since the last maintainer response.
const changesRequestedOutstanding =
pr.reviewDecision === 'CHANGES_REQUESTED' &&
(state.lastContributorActivityAt === null ||
(state.lastMaintainerResponseAt !== null &&
state.lastContributorActivityAt <= state.lastMaintainerResponseAt))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

Base outstanding change requests on the requesting review and PR author.

These aggregate timestamps conflate unrelated participants and maintainer comments with the review lifecycle. For example, an author can push after a change request, then a later maintainer comment makes the PR wait on the author again while reviewDecision remains CHANGES_REQUESTED.

Compare author comments/commits against the latest changes-requested review instead, and add regression coverage for intervening third-party and maintainer comments.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintainer/classify.ts` around lines 170 - 176, Update
changesRequestedOutstanding to use the latest CHANGES_REQUESTED review and
compare only PR-author comments or commits occurring after that review; stop
relying on aggregate lastContributorActivityAt and lastMaintainerResponseAt
timestamps. Preserve the outstanding state when no author activity follows the
requesting review, and add regression coverage for intervening third-party and
maintainer comments.

Comment on lines +125 to +130
labels(first: 20) { nodes { name } }
assignees(first: 10) { nodes { login } }
files(first: 100) { nodes { path } }
closingIssuesReferences(first: 10) { nodes { number } }
commits(last: 1) { nodes { commit { statusCheckRollup { state } } } }
timelineItems(last: 100, itemTypes: [ISSUE_COMMENT, PULL_REQUEST_REVIEW, PULL_REQUEST_COMMIT]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ—„οΈ Data Integrity & Integration | 🟠 Major | πŸ—οΈ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the collector and relevant consumers
git ls-files | rg '^(scripts/maintainer/collect\.ts|src/|scripts/)' || true

echo '--- collect.ts outline ---'
ast-grep outline scripts/maintainer/collect.ts --view expanded || true

echo '--- search for normalized completeness metadata / truncation handling ---'
rg -n "complete|completeness|truncat|pageInfo|hasNextPage|labels\\(|assignees\\(|files\\(|closingIssuesReferences\\(|timelineItems\\(" scripts src -g '!**/node_modules/**' || true

Repository: TanStack/ai

Length of output: 4588


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' scripts/maintainer/collect.ts

Repository: TanStack/ai

Length of output: 7179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- types.ts outline ---'
ast-grep outline scripts/maintainer/types.ts --view expanded || true

echo '--- classify.ts outline ---'
ast-grep outline scripts/maintainer/classify.ts --view expanded || true

echo '--- collect.ts relevant remainder ---'
sed -n '240,420p' scripts/maintainer/collect.ts

echo '--- search for files/labels/timeline consumers ---'
rg -n "\.files\b|\.labels\b|\.assignees\b|timeline|closingIssuesReferences|changedFiles|isAnswered|upvoteCount|statusCheckRollup" scripts/maintainer -g '!**/*.test.ts' || true

Repository: TanStack/ai

Length of output: 15367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- types.ts outline ---'
ast-grep outline scripts/maintainer/types.ts --view expanded || true

echo '--- classify.ts outline ---'
ast-grep outline scripts/maintainer/classify.ts --view expanded || true

echo '--- collect.ts remainder ---'
sed -n '240,420p' scripts/maintainer/collect.ts

echo '--- downstream consumers of snapshot fields ---'
rg -n "\b(files|labels|assignees|timeline|comments|closingIssuesReferences|changedFiles|isDraft|reviewDecision|statusCheckRollup)\b" scripts/maintainer/classify.ts scripts/maintainer/route.ts scripts/maintainer/actions.ts scripts/maintainer/scorecard.ts scripts/maintainer/sweep.ts scripts/maintainer/metrics.ts scripts/maintainer/comments.ts scripts/maintainer/discord.ts scripts/maintainer/types.ts || true

Repository: TanStack/ai

Length of output: 16999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' scripts/maintainer/classify.ts
echo '---'
sed -n '220,390p' scripts/maintainer/classify.ts
echo '---'
sed -n '1,140p' scripts/maintainer/comments.ts
echo '---'
sed -n '1,220p' scripts/maintainer/route.ts

Repository: TanStack/ai

Length of output: 17652


Don't treat capped nested connections as complete snapshots. files, closingIssuesReferences, timelineItems, and discussion comments are all truncated here, but downstream triage uses them directly; that can hide changesets/E2E coverage, miss maintainer replies or marker comments, and undercount linked issues. Carry completeness metadata through the snapshot or suppress automation when a connection is truncated.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintainer/collect.ts` around lines 125 - 130, Update the snapshot
collection logic around the GraphQL connections files, closingIssuesReferences,
timelineItems, and discussion comments so capped results are not treated as
complete data. Propagate each connection’s pagination/completeness metadata
through the collected snapshot, and ensure downstream triage automation either
fetches all pages or suppresses decisions when any required connection is
truncated.

Comment on lines +201 to +209
for (const chunk of chunkEmbeds(embeds)) {
const response = await fetchImpl(webhookUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
embeds: chunk,
allowed_mentions: { parse: ['users'] },
}),
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

Bound the Discord webhook request.

Without an abort signal or timeout, a stalled network request can hold the scheduled workflow until its job timeout. Add a bounded request timeout and cover the failure path.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintainer/discord.ts` around lines 201 - 209, Bound the webhook
request in the chunkEmbeds loop by adding an AbortController-based timeout to
the fetchImpl call, passing its signal and clearing the timer when the request
settles. Handle timeout or abort failures explicitly so the scheduled workflow
exits through the intended failure path, and add coverage for the
stalled-request case.

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the reviewed file and relevant Discord payload construction
git ls-files scripts/maintainer/discord.ts
ast-grep outline scripts/maintainer/discord.ts --view expanded || true

# Read the surrounding lines around the reported snippet
sed -n '150,240p' scripts/maintainer/discord.ts

# Find any other Discord webhook/message payload usage in the repo
rg -n --hidden --no-ignore-vcs "allowed_mentions|embeds:|content:" scripts/maintainer scripts || true

Repository: TanStack/ai

Length of output: 4193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the embed construction and mention helper
sed -n '1,170p' scripts/maintainer/discord.ts

# Pull Discord webhook/message docs if available from the public docs page
python3 - <<'PY'
import urllib.request
url = "https://discord.com/developers/docs/resources/webhook#execute-webhook"
try:
    with urllib.request.urlopen(url, timeout=20) as r:
        data = r.read().decode("utf-8", "replace")
    # Print only mention-related snippets
    for needle in ["allowed_mentions", "embeds", "content", "mentions", "parse"]:
        idx = data.lower().find(needle.lower())
        if idx != -1:
            start = max(0, idx - 250)
            end = min(len(data), idx + 700)
            print(f"\n--- {needle} ---\n")
            print(data[start:end])
except Exception as e:
    print(f"FETCH_ERROR: {e}")
PY

Repository: TanStack/ai

Length of output: 5884


🌐 Web query:

Discord webhook allowed_mentions embeds mentions content docs

πŸ’‘ Result:

The allowed_mentions object in the Discord webhook payload is a top-level property used to control which mentions (users, roles, or @everyone/@here) will trigger a notification [1][2]. Key technical points regarding allowed_mentions and embeds: Scope: The allowed_mentions object applies to the entire webhook message, including both the message content and any embeds [1]. It must be placed at the top level of the JSON payload; if placed inside an embed object, it will be ignored [1]. Functionality: It determines whether a mention string (e.g., <@123>, <@&456>, @everyone) renders as a functional mention that pings a user or role, or if it renders merely as plain text [2]. Mentions within Embeds: While you can include mention syntax (such as <@ID> or <@&ID>) in the text fields of an embed (like title or description), Discord does not support active pings within embeds [3]. These strings will typically render as text rather than clickable, notifying mentions regardless of the allowed_mentions configuration [3]. Configuration: 1. Parse Array: You can specify an array in the parse field containing "users", "roles", or "everyone" to allow auto-parsing for those categories [2]. 2. Whitelisting: Alternatively, you can use the users or roles arrays to whitelist specific IDs [1][2]. 3. Constraints: You cannot use the parse field and the explicit users/roles arrays in the same payload; doing so will result in a 400 Bad Request error [2]. For messages containing both content and embeds, a single top-level allowed_mentions object governs all mention parsing for the message [1]. Top results: [4][5][6][1][2][3]

Citations:


🌐 Web query:

site:discord.com/developers/docs resources webhook allowed_mentions embeds content mention in embed no ping

πŸ’‘ Result:

To mention a user in a Discord webhook message without triggering a notification (ping), you should use the allowed_mentions object in your webhook payload. By setting the parse array to exclude users or roles, you can prevent the automatic pinging behavior that usually accompanies mentions. The allowed_mentions object allows you to control which mentions are parsed and result in a notification. To prevent all pings, you can use an empty parse array or explicitly exclude specific mention types. Example of an allowed_mentions payload structure: { "content": "Hello <@123456789012345678>", "allowed_mentions": { "parse": [] } } When sending content in an embed, the same allowed_mentions rules apply. Discord parses mentions within embed fields (such as title, description, and field values) based on the configuration provided in the allowed_mentions object. If you do not want the mention in the embed to ping the user, ensure that the allowed_mentions block is included in your request and that it does not explicitly allow user or role mentions (or keeps the parse array empty). Keep in mind that if you include a user ID in the mention format <@USER_ID> or <@!USER_ID> within your content or embed, Discord will render it as a mention. Using the empty parse array in allowed_mentions ensures that these mentions are treated as text only, preventing the associated notification.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the embed builder and mention helper in full
cat -n scripts/maintainer/discord.ts | sed -n '1,170p'

Repository: TanStack/ai

Length of output: 6927


Move maintainer mentions into top-level content. The mention() helper currently renders <@...> inside an embed description, and this webhook sends only embeds, so those maintainer pings won’t notify anyone. Put the mentions in content for the message that should alert maintainers, and whitelist just those IDs in allowed_mentions.users.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintainer/discord.ts` around lines 201 - 209, Update the webhook
payload in the embed-sending loop to place maintainer mentions in the top-level
content field rather than only inside embed descriptions. Use the existing
mention-related symbols to build that content for alerting messages, and set
allowed_mentions.users to exactly the corresponding maintainer IDs while
preserving the existing embed chunking and POST behavior.

Comment thread scripts/maintainer/github.ts
Comment thread scripts/maintainer/metrics.ts
Comment thread scripts/maintainer/metrics.ts
Comment thread scripts/maintainer/metrics.ts Outdated
@tombeckenham tombeckenham added the waiting-on: author Waiting for the author to respond or update label Jul 23, 2026
… merge-conflicts label

- Workflows: grant issues/pull-requests/discussions read scopes the GraphQL
  snapshot needs (scorecard had only contents: read)
- classify: readyToMerge requires explicit MERGEABLE + non-pending CI;
  ack/repro markers only count in bot comments; freshContributorReply now
  requires a maintainer response to exist (and is computed for issues too)
- metrics: fresh replies use the real transition for issues, closed response
  samples exclude roster authors, suspected-spam PRs leave the assignment
  denominator
- config: repo must be exactly one owner/name pair
- github/discord: per-request abort timeouts
- actions: 1s pacing between mutations, one 60s retry on 403/429, and
  per-mutation progress output (a silent loop tripped a run watchdog)
- new managed merge-conflicts label on conflicting PRs, reconciled like the rest
- discord: flagged items lead the New embed with their own count
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: author Waiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants