mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-22 04:40:44 +02:00
* 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
43 lines
2.5 KiB
TypeScript
43 lines
2.5 KiB
TypeScript
import type { AskUserQuestionFingerprint } from './claude-pty-runner';
|
|
|
|
/** Seeded-count fixtures cover native review cadence; outside voices have separate evals. */
|
|
export function pickDesignCountOutsideVoices(
|
|
_routing: AskUserQuestionFingerprint,
|
|
active: AskUserQuestionFingerprint,
|
|
): number | null {
|
|
const call = active.nativeCall;
|
|
let question: string;
|
|
let labels: string[];
|
|
if (call) {
|
|
if (call.answered || call.failed) return null;
|
|
const index = active.nativeQuestionIndex ?? (call.questions.length === 1 ? 0 : undefined);
|
|
if (index === undefined || !Number.isInteger(index) || index < 0 || index >= call.questions.length) return null;
|
|
const identity = `${call.sessionId}:${call.toolUseId}` +
|
|
(call.questions.length > 1 ? `:question:${index}` : '');
|
|
if (active.signature !== identity) return null;
|
|
const q = call.questions[index]!;
|
|
if (q.multiSelect || !/^outside(?: design)? voices$/i.test(q.header.trim()) ||
|
|
!/<gstack-qid:(?:outside-voices-design|plan-design-review-outside-voices)>/.test(q.question)) return null;
|
|
question = q.question;
|
|
labels = q.options.map(option => option.label);
|
|
} else {
|
|
// Native JSONL can arrive after the answer. The caller supplies the
|
|
// active viewport fingerprint; a known but unmatched packet is blocked
|
|
// before this hook. Require the specific opt-in premise and both actions.
|
|
question = active.promptSnippet;
|
|
const packetBar = /^←[^→]*[☐☒]\s+Outside voices\b[^→]*✔\s*Submit\s*→\s*[│┃]?\s*/i.exec(question);
|
|
if (packetBar) question = question.slice(packetBar[0].length);
|
|
else if (!/^(?:[☐□]\s*)?outside(?: design)? voices\b/i.test(question)) return null;
|
|
labels = active.options.map(option => option.label);
|
|
while (labels.length > 2 && /^(?:Type something\.?|Chat about this)$/i.test(labels.at(-1)!.trim())) labels.pop();
|
|
}
|
|
if (!/\b(?:want|run|include|enable)\b[^?]{0,90}\boutside(?: design)? voices\b/i.test(question) ||
|
|
!/\b(?:before|for)\s+(?:the\s+)?(?:detailed\s+)?(?:design\s+)?review\b/i.test(question)) return null;
|
|
labels = labels.map(label => label.trim().replace(/\s*\(recommended\)\s*$/i, ''));
|
|
if (labels.length !== 2) return null;
|
|
const yes = labels.map(label => /^Yes,?\s+run outside(?: design)? voices$/i.test(label));
|
|
const no = labels.map(label => /^No,?\s+proceed without$/i.test(label));
|
|
if (yes.filter(Boolean).length !== 1 || no.filter(Boolean).length !== 1) return null;
|
|
return no.findIndex(Boolean) + 1;
|
|
}
|