import type { UiImpact, WorkflowFacts } from "./domain.ts"; const IMPACT_RANK: Record = { none: 0, minor: 1, "non-trivial": 2, }; const NON_TRIVIAL_TERMS = new Set([ "accessibility", "a11y", "component", "dialog", "editor", "form", "layout", "modal", "navigation", "onboarding", "page", "redesign", "responsive", "settings", "sidebar", "wizard", ]); const NON_TRIVIAL_PATH_PARTS = [ "/+page.", "/dialog.", "/form.", "/layout.", "/modal.", "/navigation.", "/nav.", "/settings.", "/shell.", "/sidebar.", "/accessibility/", "/onboarding/", "/settings/", ]; const UI_SOURCE_SUFFIXES = [ ".astro", ".css", ".html", ".jsx", ".less", ".scss", ".svelte", ".tsx", ".vue", ]; export interface UiImpactPolicyInput { plannerImpact: UiImpact; plannerEvidence: string[]; affectedSurfaces: string[]; facts: WorkflowFacts; } export interface UiImpactPolicyResult { impact: UiImpact; evidence: string[]; affectedSurfaces: string[]; objectiveImpact: UiImpact; } function objectiveEvidence(facts: WorkflowFacts) { const evidence: string[] = []; for (const term of facts.requestTerms) { if (NON_TRIVIAL_TERMS.has(term.toLowerCase())) { evidence.push(`Request includes non-trivial UI signal: ${term}`); } } for (const candidatePath of facts.candidatePaths) { const normalized = `/${candidatePath.toLowerCase().replaceAll("\\", "/")}`; const isUiSource = UI_SOURCE_SUFFIXES.some((suffix) => normalized.endsWith(suffix), ); if ( isUiSource && NON_TRIVIAL_PATH_PARTS.some((part) => normalized.includes(part)) ) { evidence.push(`Affected path has non-trivial UI responsibility: ${candidatePath}`); } } for (const tag of [...facts.tags, ...facts.riskTags]) { if (NON_TRIVIAL_TERMS.has(tag.toLowerCase())) { evidence.push(`Repository evidence includes non-trivial UI signal: ${tag}`); } } return evidence; } export function enforceUiImpact(input: UiImpactPolicyInput): UiImpactPolicyResult { const objective = objectiveEvidence(input.facts); const objectiveImpact: UiImpact = objective.length > 0 ? "non-trivial" : input.plannerImpact; const impact = IMPACT_RANK[objectiveImpact] > IMPACT_RANK[input.plannerImpact] ? objectiveImpact : input.plannerImpact; return { impact, objectiveImpact, evidence: [...new Set([...input.plannerEvidence, ...objective])], affectedSurfaces: [...new Set(input.affectedSurfaces)], }; }