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.

src/daily-check.ts

69 lines · 2.0 KB · TypeScript
import { kody, packageStorage } from 'kody:runtime'
import checkPrHealth from './index.ts'

const STATE_KEY = 'last-seen-fingerprints'

type SeenState = Record<string, string>

/**
 * Job entry: check PR health and mail the account inbox only when something
 * changed since the last run. A PR that was already failing yesterday and is
 * still failing today is not news, so it does not send mail — an unconditional
 * daily digest trains you to ignore the real one.
 *
 * @returns Counts for the run plus whether mail was sent
 *
 * @example
 * import dailyCheck from 'kody:@gsimone/pr-health/daily-check'
 *
 * await dailyCheck()
 */
export default async function dailyCheck(): Promise<{
	examined: number
	stuck: number
	changed: number
	notified: boolean
}> {
	const storage = packageStorage()
	const previous = ((await storage.get(STATE_KEY)) ?? {}) as SeenState

	const { examined, findings, accessWarning } = await checkPrHealth()

	const current: SeenState = {}
	for (const finding of findings) {
		current[`${finding.repo}#${finding.number}`] = finding.fingerprint
	}

	// News = newly stuck, or stuck for a different reason than last time.
	const changed = findings.filter((finding) => {
		const key = `${finding.repo}#${finding.number}`
		return previous[key] !== finding.fingerprint
	})

	await storage.set(STATE_KEY, current)

	if (changed.length === 0) {
		return { examined, stuck: findings.length, changed: 0, notified: false }
	}

	const noun = changed.length === 1 ? 'PR' : 'PRs'
	const lines = changed.map((finding) => {
		const why = finding.reasons.map((reason) => reason.detail).join('; ')
		return `${finding.repo} #${finding.number} — ${finding.title}\n  ${why}\n  ${finding.url}`
	})

	const footer = [`(${findings.length} stuck total, ${examined} examined.)`]
	if (accessWarning) footer.push(accessWarning)

	await kody.emailSend({
		subject: `${changed.length} ${noun} need attention`,
		text: `${lines.join('\n\n')}\n\n${footer.join('\n')}`,
	})

	return {
		examined,
		stuck: findings.length,
		changed: changed.length,
		notified: true,
	}
}