← Public packages
@kody/doctor
Read-only account hygiene: open run errors, job alarm drift, secret host gaps, and plan pressure.
src/index.ts
313 lines · 9.7 KB · TypeScriptimport { kody } from 'kody:runtime'
const OPEN_ERROR_SAMPLE = 5
const MAX_DETAIL = 180
export type DoctorSeverity = 'error' | 'warning' | 'info'
export type DoctorFinding = {
id: string
severity: DoctorSeverity
title: string
detail: string
nextAsk: string
}
export type CheckInput = {
/** Optional ISO 8601 lower bound for run_summary and the open-error sample. */
since?: string
}
export type DoctorReport = {
ok: boolean
generatedAt: string
summary: {
openErrors: number
running: number
enabledJobsWithLastError: number
killSwitchedJobs: number
expiredJobs: number
alarmStatus: string
secretsMissingHosts: number
staleForks: number
usageWarnings: number
}
findings: Array<DoctorFinding>
markdown: string
}
type Settled<T> = { ok: true; value: T } | { ok: false; error: string }
/**
* Read-only account hygiene report: open run errors, job and alarm problems,
* user secrets with no allowed hosts, stale community forks, and plan pressure.
*
* Never mutates runs, jobs, secrets, or packages.
*/
export default async function check(input: CheckInput = {}): Promise<DoctorReport> {
const since = typeof input.since === 'string' && input.since.trim() ? input.since : undefined
const runFilter = since ? { since } : {}
const [summaryResult, openRunsResult, jobsResult, secretsResult, packagesResult, usageResult] =
await Promise.all([
settle(() => kody.runSummary(runFilter)),
settle(() =>
kody.runList({
...runFilter,
error_triage: 'open',
status: 'error',
limit: OPEN_ERROR_SAMPLE,
}),
),
settle(() => kody.jobList({})),
settle(() => kody.secretList({ scope: 'user' })),
settle(() => kody.packageList({})),
settle(() => kody.usageGet({})),
])
const findings: Array<DoctorFinding> = []
pushCapabilityFailure(findings, 'runs.summary', summaryResult)
pushCapabilityFailure(findings, 'runs.list', openRunsResult)
pushCapabilityFailure(findings, 'jobs.list', jobsResult)
pushCapabilityFailure(findings, 'secrets.list', secretsResult)
pushCapabilityFailure(findings, 'packages.list', packagesResult)
pushCapabilityFailure(findings, 'account.usage', usageResult)
const summary = summaryResult.ok ? summaryResult.value : null
const openRuns = openRunsResult.ok ? openRunsResult.value.runs : []
const jobs = jobsResult.ok ? jobsResult.value.jobs : []
const alarm = jobsResult.ok ? jobsResult.value.alarm : null
const secrets = secretsResult.ok ? secretsResult.value.secrets : []
const packages = packagesResult.ok ? packagesResult.value.packages : []
const usage = usageResult.ok ? usageResult.value : null
if (summary && summary.errors > 0) {
const bySurface = summary.by_surface
.filter((row) => row.errors > 0)
.map((row) => `${row.surface}: ${row.errors}`)
.join(', ')
const samples = openRuns
.slice(0, OPEN_ERROR_SAMPLE)
.map((run) => {
const label = run.kody_id ?? run.name ?? run.surface
return `${run.surface} ${label}: ${clip(run.error_message ?? 'error')}`
})
.join(' · ')
findings.push({
id: 'open-run-errors',
severity: 'error',
title: `${summary.errors} open run error${summary.errors === 1 ? '' : 's'}`,
detail: [bySurface, samples].filter(Boolean).join('. '),
nextAsk:
'Ask me to inspect the latest open error with run_get, then mark noise with run_update.',
})
}
if (summary && summary.running > 0) {
findings.push({
id: 'runs-still-running',
severity: 'info',
title: `${summary.running} run${summary.running === 1 ? '' : 's'} still running`,
detail: 'These are in-flight and are not counted as open errors.',
nextAsk: 'Ask me to list running work with run_list if something looks stuck.',
})
}
const enabledWithLastError = jobs.filter(
(job) => job.enabled && !job.expired && job.last_run_status === 'error',
)
if (enabledWithLastError.length > 0) {
findings.push({
id: 'jobs-last-error',
severity: 'error',
title: `${enabledWithLastError.length} enabled job${enabledWithLastError.length === 1 ? '' : 's'} last failed`,
detail: enabledWithLastError
.slice(0, OPEN_ERROR_SAMPLE)
.map((job) => `${job.name}: ${clip(job.last_run_error ?? 'error')}`)
.join(' · '),
nextAsk: 'Ask me to open one failed job with job_get and its latest run_list rows.',
})
}
const killSwitched = jobs.filter((job) => job.kill_switch_enabled)
if (killSwitched.length > 0) {
findings.push({
id: 'jobs-kill-switch',
severity: 'warning',
title: `${killSwitched.length} job${killSwitched.length === 1 ? '' : 's'} on the kill switch`,
detail: killSwitched
.slice(0, OPEN_ERROR_SAMPLE)
.map((job) => job.name)
.join(', '),
nextAsk: 'Ask me why the kill switch is on before re-enabling any of these jobs.',
})
}
const expired = jobs.filter((job) => job.expired)
if (expired.length > 0) {
findings.push({
id: 'jobs-expired',
severity: 'warning',
title: `${expired.length} expired job${expired.length === 1 ? '' : 's'}`,
detail: expired
.slice(0, OPEN_ERROR_SAMPLE)
.map((job) => job.name)
.join(', '),
nextAsk: 'Ask me whether these schedules should be extended or removed.',
})
}
if (alarm && (alarm.status === 'out_of_sync' || alarm.status === 'missing_binding')) {
findings.push({
id: 'job-alarm',
severity: 'error',
title:
alarm.status === 'missing_binding'
? 'Job manager alarm binding is missing'
: 'Job manager alarm is out of sync',
detail: [
`status=${alarm.status}`,
alarm.next_runnable_job_id ? `next=${alarm.next_runnable_job_id}` : null,
alarm.alarm_scheduled_for ? `scheduled=${alarm.alarm_scheduled_for}` : null,
]
.filter(Boolean)
.join(' · '),
nextAsk: 'Ask me to inspect job_list alarm state and get the job manager back in sync.',
})
}
const secretsMissingHosts = secrets.filter((secret) => secret.allowed_hosts.length === 0)
if (secretsMissingHosts.length > 0) {
findings.push({
id: 'secrets-missing-hosts',
severity: 'warning',
title: `${secretsMissingHosts.length} user secret${secretsMissingHosts.length === 1 ? '' : 's'} with no allowed hosts`,
detail: secretsMissingHosts
.slice(0, 8)
.map((secret) => secret.name)
.join(', '),
nextAsk:
'Ask me to open /account/secrets and add allowed hosts before using these in outbound fetch.',
})
}
const staleForks = packages.filter((pkg) => pkg.source_listing_id && pkg.listing_current === false)
if (staleForks.length > 0) {
findings.push({
id: 'stale-forks',
severity: 'warning',
title: `${staleForks.length} forked package${staleForks.length === 1 ? '' : 's'} whose source listing is gone`,
detail: staleForks
.slice(0, OPEN_ERROR_SAMPLE)
.map((pkg) => pkg.name)
.join(', '),
nextAsk:
'Ask me to review each stale fork and either replace it from a current listing or delete it.',
})
}
const usageWarnings = usage?.warnings ?? []
const warned = new Set(usageWarnings.map((row) => row.resource))
const pressure = [
...usageWarnings,
...(usage?.resources ?? []).filter(
(resource) => resource.overEightyPercent && !warned.has(resource.resource),
),
]
if (pressure.length > 0) {
findings.push({
id: 'usage-pressure',
severity: 'warning',
title: `${pressure.length} plan limit${pressure.length === 1 ? '' : 's'} near or over capacity`,
detail: pressure
.slice(0, 6)
.map((row) => `${row.label}: ${row.current}/${row.limit}`)
.join(' · '),
nextAsk: `Ask me how to reduce ${pressure[0]?.label ?? 'usage'}: ${clip(pressure[0]?.howToReduce ?? 'review plan limits')}`,
})
}
const reportSummary = {
openErrors: summary?.errors ?? 0,
running: summary?.running ?? 0,
enabledJobsWithLastError: enabledWithLastError.length,
killSwitchedJobs: killSwitched.length,
expiredJobs: expired.length,
alarmStatus: alarm?.status ?? 'unknown',
secretsMissingHosts: secretsMissingHosts.length,
staleForks: staleForks.length,
usageWarnings: pressure.length,
}
const ok = findings.every((finding) => finding.severity !== 'error')
return {
ok,
generatedAt: new Date().toISOString(),
summary: reportSummary,
findings,
markdown: formatMarkdown({ ok, findings, summary: reportSummary }),
}
}
async function settle<T>(fn: () => Promise<T>): Promise<Settled<T>> {
try {
return { ok: true, value: await fn() }
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
}
}
}
function pushCapabilityFailure(findings: Array<DoctorFinding>, id: string, result: Settled<unknown>) {
if (result.ok) return
findings.push({
id: `capability-failed:${id}`,
severity: 'error',
title: `Could not read ${id}`,
detail: clip(result.error),
nextAsk: `Ask me to retry ${id} directly and inspect the error.`,
})
}
function clip(value: string) {
const text = value.replace(/\s+/g, ' ').trim()
if (text.length <= MAX_DETAIL) return text
return `${text.slice(0, MAX_DETAIL - 1)}…`
}
function formatMarkdown(input: {
ok: boolean
findings: Array<DoctorFinding>
summary: DoctorReport['summary']
}) {
const lines = [
input.ok ? '# Doctor: ok' : '# Doctor: needs attention',
'',
`- Open errors: ${input.summary.openErrors}`,
`- Running: ${input.summary.running}`,
`- Enabled jobs last failed: ${input.summary.enabledJobsWithLastError}`,
`- Kill switch: ${input.summary.killSwitchedJobs}`,
`- Expired jobs: ${input.summary.expiredJobs}`,
`- Alarm: ${input.summary.alarmStatus}`,
`- Secrets missing hosts: ${input.summary.secretsMissingHosts}`,
`- Stale forks: ${input.summary.staleForks}`,
`- Plan pressure: ${input.summary.usageWarnings}`,
]
if (input.findings.length === 0) {
lines.push('', 'No findings.')
return lines.join('\n')
}
lines.push('', '## Findings')
for (const finding of input.findings) {
lines.push(
'',
`### ${finding.severity}: ${finding.title}`,
finding.detail,
`Next: ${finding.nextAsk}`,
)
}
return lines.join('\n')
}