112 lines
2.5 KiB
TypeScript
112 lines
2.5 KiB
TypeScript
import type { UiImpact, WorkflowFacts } from "./domain.ts";
|
|
|
|
const IMPACT_RANK: Record<UiImpact, number> = {
|
|
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)],
|
|
};
|
|
}
|