← 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 · TypeScriptimport { 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,
}
}