import type { Role, RoleResult, StartStep } from "@uncaged/nerve-core"; export type IntakeMeta = { issueUrl: string; host: string | null; owner: string | null; repo: string | null; issueNumber: number | null; valid: boolean; failureKind: "none" | "invalid_input"; failureReason: string | null; }; function parseIssueUrl(raw: string): IntakeMeta { const issueUrl = raw.trim(); const match = issueUrl.match(/^https?:\/\/([^/\s]+)\/([^/\s]+)\/([^/\s]+)\/issues\/(\d+)(?:[/?#].*)?$/i); if (match === null) { return { issueUrl, host: null, owner: null, repo: null, issueNumber: null, valid: false, failureKind: "invalid_input", failureReason: `invalid issue URL: ${issueUrl}`, }; } const issueNumber = Number(match[4]); if (!Number.isInteger(issueNumber) || issueNumber <= 0) { return { issueUrl, host: null, owner: null, repo: null, issueNumber: null, valid: false, failureKind: "invalid_input", failureReason: `invalid issue number: ${match[4]}`, }; } return { issueUrl, host: match[1], owner: match[2], repo: match[3], issueNumber, valid: true, failureKind: "none", failureReason: null, }; } export function buildIntakeRole(): Role { return async (start: StartStep): Promise> => { const parsed = parseIssueUrl(start.content); if (!parsed.valid) { return { content: parsed.failureReason ?? "invalid issue URL", meta: parsed }; } return { content: `parsed issue URL: ${parsed.owner}/${parsed.repo}#${parsed.issueNumber} @ ${parsed.host}`, meta: parsed, }; }; }