Skip to content
← Public packages

@kentcdodds/kody-issue-triage

Loop-safe triage for Kody run errors and fleet package-runtime error-rate elevations. Wakes Cole (Grok Bot) to decide; Cursor agents only when Cole escalates.

src/get-issue-state.ts

70 lines · 2.0 KB · TypeScript
import { gatherPeerContext } from './peer-context.ts'
import { emptyPeerContext, siblingWhy } from './peer-match.ts'
import { hourBucket, maxAgentsPerHour } from './shared.ts'
import {
	getCounter,
	getFingerprint,
	getLease,
	initSchema,
	listSiblingFingerprints,
} from './storage.ts'

export type GetIssueStateInput = {
	fingerprint: string
}

/**
 * Loop-guard lookup for triage agents: prior fingerprint state plus hourly caps.
 */
export default async function getIssueState(input: GetIssueStateInput) {
	const fingerprint = String(input?.fingerprint || '').trim()
	if (!fingerprint) throw new Error('fingerprint is required')
	await initSchema()
	const record = await getFingerprint(fingerprint)
	const bucket = hourBucket()
	const spawnedThisHour = await getCounter(`spawned:${bucket}`)
	const fingerprintsThisHour = await getCounter(`fingerprints:${bucket}`)
	const eventsThisHour = await getCounter(`events:${bucket}`)
	const lease = await getLease()
	let peerContext = emptyPeerContext()
	if (record) {
		try {
			const stored = await listSiblingFingerprints(record, 20)
			peerContext = {
				...emptyPeerContext(),
				siblings: stored.map((sibling) => ({
					fingerprint: sibling.fingerprint,
					owner: sibling.owner,
					error_family: sibling.error_family,
					status: sibling.status,
					outcome: sibling.outcome,
					completedAt: sibling.completed_at,
					agentUrl: sibling.agent_url || sibling.kody_agent_url,
					summary: sibling.summary,
					why: siblingWhy(sibling, record),
				})),
			}
		} catch {
			peerContext = emptyPeerContext()
		}
		try {
			const full = await gatherPeerContext(record)
			peerContext = {
				...full,
				siblings: full.siblings.length ? full.siblings : peerContext.siblings,
			}
		} catch {
			// Keep the cheap SQL sibling snapshot when Cursor/GitHub gathering fails.
		}
	}
	return {
		record,
		alreadyEscalatedToKody: Boolean(record?.kody_agent_id),
		spawnedThisHour,
		fingerprintsThisHour,
		eventsThisHour,
		capRemaining: Math.max(0, maxAgentsPerHour - spawnedThisHour),
		lease,
		peerContext,
	}
}