/** * queue-scan.ts — Issue queue scanning helpers. * * Shared by: tick (projectTick), work-start (auto-pickup), and other consumers * that need to find queued issues or detect roles/levels from labels. */ import type { Issue, StateLabel } from "../providers/provider.js"; import type { IssueProvider } from "../providers/provider.js"; import { getLevelsForRole, getAllLevels } from "../roles/index.js"; import { getQueueLabels, getAllQueueLabels, detectRoleFromLabel as workflowDetectRole, type WorkflowConfig, type Role, } from "../workflow.js"; // --------------------------------------------------------------------------- // Label detection // --------------------------------------------------------------------------- export function detectLevelFromLabels(labels: string[]): string | null { const lower = labels.map((l) => l.toLowerCase()); // Match role.level labels (e.g., "dev.senior", "qa.mid", "architect.junior") for (const l of lower) { const dot = l.indexOf("."); if (dot === -1) continue; const role = l.slice(0, dot); const level = l.slice(dot + 1); const roleLevels = getLevelsForRole(role); if (roleLevels.includes(level)) return level; } // Fallback: plain level name const all = getAllLevels(); return all.find((l) => lower.includes(l)) ?? null; } /** * Detect role from a label using workflow config. */ export function detectRoleFromLabel( label: StateLabel, workflow: WorkflowConfig, ): Role | null { return workflowDetectRole(workflow, label); } // --------------------------------------------------------------------------- // Issue queue queries // --------------------------------------------------------------------------- export async function findNextIssueForRole( provider: Pick, role: Role, workflow: WorkflowConfig, ): Promise<{ issue: Issue; label: StateLabel } | null> { const labels = getQueueLabels(workflow, role); for (const label of labels) { try { const issues = await provider.listIssuesByLabel(label); if (issues.length > 0) return { issue: issues[issues.length - 1], label }; } catch { /* continue */ } } return null; } /** * Find next issue for any role (optional filter). Used by work_start for auto-detection. */ export async function findNextIssue( provider: Pick, role: Role | undefined, workflow: WorkflowConfig, ): Promise<{ issue: Issue; label: StateLabel } | null> { const labels = role ? getQueueLabels(workflow, role) : getAllQueueLabels(workflow); for (const label of labels) { try { const issues = await provider.listIssuesByLabel(label); if (issues.length > 0) return { issue: issues[issues.length - 1], label }; } catch { /* continue */ } } return null; }