Files
gstack/test/helpers/design-ui-scope.ts
T
Garry Tan 35dd014c58 v1.87.5.0 perf: remove idle waits from tests and CI planning (#2897)
* v1.87.5.0 perf: remove idle waits from tests and CI planning

* fix: settle split PTY redraws before routing input

* docs: record final burst-safe test benchmarks

* fix: keep cold-setup snapshot metadata dependency-free

* fix: avoid early-reader pipe races in artifact URL parsing

* fix: preserve safety matches for multiline command payloads

* fix: recognize concurrent CSO publication removal

* test: preload the UI design-review target before invocation

* docs: record validation blocker fixes

* fix: bind plan observer rejection to the invoked command

* fix: count only native design decisions in the UI gate

* docs: clarify UI-positive eval evidence requirements

* test: recognize native UI decisions without weakening finding counts

* test: decouple native UI evidence from question punctuation

* test: recognize concrete native UI decisions independently of prose format

* fix: retain failed eval logs under the hidden CI cache

* test: await telemetry completion instead of racing disk writes
2026-09-21 12:27:25 -04:00

28 lines
2.1 KiB
TypeScript

import type { AskUserQuestionFingerprint } from './claude-pty-runner';
import { isDesignCountFirstReview } from './design-count-review';
export function isDesignUIScopeReview(fp: AskUserQuestionFingerprint): boolean {
const call = fp.nativeCall;
if (!call?.answered || call.failed || !Array.isArray(call.unansweredQuestionIndices) ||
call.unansweredQuestionIndices.length || !call.questions.length ||
fp.signature !== `${call.sessionId}:${call.toolUseId}`) return false;
if (call.questions.some(q => q.multiSelect || q.options.length < 2 ||
new Set(q.options.map(option => option.label)).size !== q.options.length ||
!q.options.some(option => option.label === call.answers?.[q.question]))) return false;
if (isDesignCountFirstReview(fp)) return true;
const workflow = /\b(?:review(?:s|ers?)?|scope|setup|learnings|routing|mockups?|permissions?|codex|claude|outside)\b/i;
const ui = /\b(?:dashboard|hierarchy|panels?|layout|headers?|buttons?|navigation|notifications?|activity|actions?|spacing|colou?rs?|fonts?|typography|loading|errors?|focus|contrast|keyboard|mobile|responsive|toasts?|modals?|empty)\b/i;
return call.questions.some(q => {
if (/^(?:scope|focus|learnings|routing|next steps?|outside(?: design)? voices)$/i.test(q.header.trim())) return false;
const issue = /^(?:D\d+\s*[—–:-]\s*)?Issue ([1-9]\d*)\s*[:—–-]\s*([^\n]+\?)$/i.exec(q.question.split('\n')[0]!.trim());
if (!issue || workflow.test(issue[2]!) || !ui.test(issue[2]!) ||
!q.options.some(option => ui.test(`${option.label} ${option.description ?? ''}`))) return false;
const context = /^Project\/branch\/task:([^\n]*)/mi.exec(q.question)?.[1] ?? '';
const namedPlans = context.match(/\b[\w.-]+\.md\b/gi) ?? [];
if ((namedPlans.length && !namedPlans.some(plan => /^PLAN\.md$/i.test(plan))) ||
/\b(?:before|prior to)\s+Pass\b/i.test(context)) return false;
const choice = new RegExp(`^${issue[1]}[A-Z](?:[).:—–-]\\s*|\\s+)\\S`);
return q.options.every(option => choice.test(option.label) && !workflow.test(option.label));
});
}