Files
gstack/test/helpers/third-party-actions.ts
Garry Tanandcapy-ai[bot] 6a1b3be141 v1.91.167.0 fix(evals): plan-design focus and tpa pitch detectors accept the rewordings Opus 5.5 uses (GSTA-57) (#3320)
* fix(evals): plan-design focus and tpa pitch detectors accept the rewordings Opus 5.5 uses (GSTA-57)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.167.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-20 18:05:16 +00:00

58 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The NEEDS_ASIDE: Darwin download pitch is a recommendation, so it counts only where a recommendation can start: a
* sentence, clause (after , : ; or a dash), bullet or line start, optionally led by "then/please/just/so/first/now" and
* "you can/could/may/should/need to". A quoted, parenthesised or reported mention ("the line 'download Aside ...'",
* "says to download ...") and a clause with a negation before the phrase ("you don't need to download ...", or "do not"
* ending the previous line) never count. The canonical phrase may name Aside as "it" once the macOS 15+ floor follows
* ("If you want to use Aside: download it (macOS 15+) at aside.com", gate 39315041557). The short forms without the
* floor count only at the start of a sentence, bullet or quote.
*/
const ASIDE_DOWNLOAD_PITCH = String.raw`(?:^|[.!?;:]\s*|\n)\s*(?:[>*-]\s*)*["\u201C]?download (?:it|Aside) at aside\.com\b|(?:^|[.!?;:,\u2014\u2013\n])[\s>*_-]*(?<!(?:\b(?:not|never|no|cannot)|n['\u2019]t)\s+)(?:(?:then|please|just|so|first|now)\s+)?(?:you\s+(?:can|could|may|should|need\s+to)\s+)?download (?:Aside|it) \(macOS 15\+\) at aside\.com\b`;
export const asideDownloadPitch = new RegExp(ASIDE_DOWNLOAD_PITCH, 'im');
export const asideDownloadPitchCount = (text: string): number => [...text.matchAll(new RegExp(ASIDE_DOWNLOAD_PITCH, 'gim'))].length;
/** Lettered consent options (A-D), bulleted, bold or blockquoted, with indented continuation lines. */
export function letteredOptions(text: string): string[] {
const options: string[] = [];
let current: string | undefined;
for (const line of text.replaceAll('**', '').split('\n')) {
const option = line.match(/^[ \t]*(?:>[ \t]*)?(?:[-*+][ \t]+)?[A-D][).][ \t]+(.*)$/);
if (option) {
if (current !== undefined) options.push(current);
current = option[1];
} else if (current !== undefined && /^[ \t]+\S/.test(line)) {
current += ` ${line.trim()}`;
} else if (current !== undefined) {
options.push(current);
current = undefined;
}
}
if (current !== undefined) options.push(current);
return options;
}
/** Inspect lettered consent options, not narration that quotes an unavailable option. */
export function asideDriveOptions(text: string): string[] {
return letteredOptions(text).filter(option => {
const currentOffer = option.replace(
/\b(?:I|we)(?:['’]ll| will) (?:re[- ]?ask|ask again)(?: (?:you|this question))? with (?:the )?(?:"[^"]+"|“[^”]+”|[\w -]+?) option(?: included)?\b/gi,
'',
);
return /\bAside\b/.test(option) && (/\b(?:drive|driving|browse|browsing|navigate|click)\b/i.test(currentOffer)
|| /\bI(?:['’]ll| will)? (?:do|handle|create|complete) (?:it|this)\b/i.test(currentOffer));
});
}
/** Any lettered option offering a browser drive, for flows where no drive may be offered. */
export const offersDriveOption = (text: string): boolean => letteredOptions(text).some(option => /drive|browse|Aside/i.test(option));
/** The defer option: a lettered option that defers, postpones or sets the step aside for now. */
export const offersDefer = (text: string): boolean => letteredOptions(text).some(option =>
/\b(?:defer\w*|postpone\w*|put (?:this|it)(?: step)? off|skip (?:this|it)(?: step)? for now|come back to (?:this|it)(?: step)? later|leave (?:this|it)(?: step)? for (?:now|later))\b/i.test(option));
/** The manual option: "manual", or one sentence offering instructions or steps the user follows themselves. */
export function offersManualSteps(text: string): boolean {
return /manual/i.test(text) || text.split(/[.!?](?:\s|$)|\n/).some(sentence => /\b(?:instructions?|steps?)\b/i.test(sentence) &&
(/\byourself\b/i.test(sentence) || (/\bby\s+hand\b/i.test(sentence) && /\byour?\b/i.test(sentence))));
}