Skip to content
← Public packages

@gsimone/pr-health

Flag your open PRs that are stuck: red CI, merge conflicts, unanswered review-bot comments, or gone quiet.

AGENTS.md

73 lines · 2.5 KB · Markdown

PR Health — agent notes

Imports

import checkPrHealth from 'kody:@gsimone/pr-health'
import dailyCheck from 'kody:@gsimone/pr-health/daily-check'

src/classify.ts holds the pure rules (classify, formatDigest, REVIEW_BOTS, ABANDONED_DAYS). It is internal — imported by the two exports above, not published as its own entry point, since it has no default export.

Smoke tests

On-demand check (read-only, safe to run any time):

import checkPrHealth from 'kody:@gsimone/pr-health'

export default async function main() {
	const { viewer, examined, findings, accessWarning } = await checkPrHealth()
	return { viewer, examined, stuck: findings.length, accessWarning }
}

Job wrapper — sends real email when the finding set changed since the last run. Run it once to verify, then enable the schedule:

import dailyCheck from 'kody:@gsimone/pr-health/daily-check'

export default async function main() {
	return await dailyCheck()
}

Calling dailyCheck() twice in a row is the useful test: the second call should report notified: false, proving the quiet-unless-changed behavior works.

Secrets

Uses {{secret:githubPat}} in the Authorization header. The value is substituted at the network boundary — no code here ever reads it. Requires api.github.com on the secret's approved hosts.

Storage

packageStorage() holds one key, last-seen-fingerprints: a map of owner/repo#number → sorted reason kinds. It is what makes the job quiet. Clear it to force the next run to report everything:

import { packageStorage } from 'kody:runtime'
// inside a package export
await packageStorage().delete('last-seen-fingerprints')

Note packageStorage() throws in ad hoc execute — it needs package provenance, so exercise it through a package export.

Edge cases

  • Partial GraphQL failures are normal. GitHub returns data and errors when an org refuses the token (long-lived fine-grained PAT, SAML, IP allow list). graphql() keeps the data; describeAccessErrors summarizes the rest into accessWarning. Do not make these fatal.
  • Bot list is a heuristic. REVIEW_BOTS in classify.ts matches on login prefix and strips a [bot] suffix. Add new bots there.
  • The absent stale rule is intentional. See README. Do not add one back without re-testing the hit rate against the real account first.
  • ABANDONED_DAYS = 120 suppresses a PR entirely, before any other rule. A PR idle for years will never be reported, by design.