From db72f47498fd71f325ef03de5316daccc8cc3466 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 8 Sep 2026 05:41:23 +0000 Subject: [PATCH] test: recognize CEO mode labels without terminal spacing The paid workflow rendered SCOPEEXPANSION at option 4, but its driver required a literal space. Match the leading mode title without cursor-spacing artifacts and ignore adjacent preview text. Preserve missing-target failures and downstream posture assertions. --- test/ceo-mode-option.test.ts | 70 ++++++++++++++++++++ test/helpers/ceo-mode-option.ts | 26 ++++++++ test/helpers/touchfiles-data.ts | 2 +- test/skill-e2e-plan-ceo-mode-routing.test.ts | 14 ++-- 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 test/ceo-mode-option.test.ts create mode 100644 test/helpers/ceo-mode-option.ts diff --git a/test/ceo-mode-option.test.ts b/test/ceo-mode-option.test.ts new file mode 100644 index 000000000..8f98862f1 --- /dev/null +++ b/test/ceo-mode-option.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, test } from 'bun:test'; +import { findCeoModeOption } from './helpers/ceo-mode-option'; +import { parseNumberedOptions, stripAnsi } from './helpers/claude-pty-runner'; +import { E2E_TOUCHFILES, selectTests } from './helpers/touchfiles'; + +describe('CEO mode option matching', () => { + test('selects option 4 from the failed Claude Code 2.1.257 menu capture', () => { + // Labels and side-pane residue from the 2026-09-08 paid failure. The + // option existed; literal includes("SCOPE EXPANSION") could not see it. + // The failure log preserves parsed labels, not the original raw frame. + const options = [ + { index: 1, label: 'SELECTIVEEXPANSION┌────────────────────────────────────────────────────────────────────────────────────┐\r (ecommnded) │SELECTIVEEXPANSION│' }, + { index: 2, label: 'HOLD SCOPE │ Hld scope: eview rigorusly fr failure modes, edg ass, observability.│' }, + { index: 3, label: 'SCOPE REDUCTION │ Then surface: cherry-pikableadditions you ca Accept/Defer/Skip.│' }, + { index: 4, label: 'SCOPEEXPANSION│Neutralposture:presentopportunities,stateeffort,youdecide.│\r │ Good for: substantialfeaturewithsolidfoundation,shippedbeforescopelock.│\r└────────────────────────────────────────────────────────────────────────────────────┘' }, + ]; + expect(findCeoModeOption(options, 'SCOPE EXPANSION')).toBe(4); + expect(findCeoModeOption(options, 'HOLD SCOPE')).toBe(2); + expect(findCeoModeOption(options, 'SELECTIVE EXPANSION')).toBe(1); + }); + + test('recognizes the mode question when every label loses its inter-word spaces', () => { + const frame = stripAnsi([ + '❯ 1. HOLD\x1b[1CSCOPE (recommended)', + ' 2. SELECTIVE\x1b[1CEXPANSION', + ' 3. SCOPE\x1b[1CEXPANSION', + ' 4. SCOPE\x1b[1CREDUCTION', + ].join('\n')); + const options = parseNumberedOptions(frame); + expect(findCeoModeOption(options, 'SCOPE EXPANSION')).toBe(3); + expect(findCeoModeOption(options, 'SCOPE REDUCTION')).toBe(4); + }); + + test('retains spaced, mixed-case labels and recommendation suffixes', () => { + expect(findCeoModeOption([ + { index: 1, label: 'Scope Expansion (recommended)' }, + { index: 2, label: 'HOLD SCOPE' }, + ], 'SCOPE EXPANSION')).toBe(1); + }); + + test('still fails on the earlier three-option capture with no expansion target', () => { + const options = [ + { index: 1, label: 'HOLD SCOPE (recommended) ┌─────────────────────────────────────────────────────────────┐' }, + { index: 2, label: 'SELECTIVE EXPANSION │HOLD SCOPE │' }, + { index: 3, label: 'SCOPE REDUCTION │ Codeiswritten.Makeitbulletproof.│' }, + ]; + expect(() => findCeoModeOption(options, 'SCOPE EXPANSION')) + .toThrow('target "SCOPE EXPANSION" not in option labels'); + }); + + test('does not select another mode because the side pane mentions the target', () => { + expect(() => findCeoModeOption([ + { index: 1, label: 'HOLD SCOPE │ SCOPE EXPANSION is another option' }, + { index: 2, label: 'SELECTIVEEXPANSION┌ SCOPEEXPANSION' }, + ], 'SCOPE EXPANSION')).toThrow('target "SCOPE EXPANSION" not in option labels'); + }); + + test('leaves unrelated navigation questions to the existing driver', () => { + expect(findCeoModeOption([ + { index: 1, label: 'Review HOLD SCOPE examples' }, + { index: 2, label: 'Choose a plan │ SCOPE EXPANSION' }, + ], 'HOLD SCOPE')).toBeNull(); + }); + + test('helper and regression changes select only the mode-routing paid eval', () => { + for (const file of ['test/helpers/ceo-mode-option.ts', 'test/ceo-mode-option.test.ts']) { + expect(selectTests([file], E2E_TOUCHFILES).selected).toEqual(['plan-ceo-mode-routing']); + } + }); +}); diff --git a/test/helpers/ceo-mode-option.ts b/test/helpers/ceo-mode-option.ts new file mode 100644 index 000000000..a542ff584 --- /dev/null +++ b/test/helpers/ceo-mode-option.ts @@ -0,0 +1,26 @@ +/** Match CEO mode labels after the PTY capture strips cursor-spacing escapes. */ +type CeoMode = 'HOLD SCOPE' | 'SCOPE EXPANSION' | 'SELECTIVE EXPANSION' | 'SCOPE REDUCTION'; + +export function findCeoModeOption( + options: ReadonlyArray<{ index: number; label: string }>, + targetMode: CeoMode, +): number | null { + const modes = options.map(option => { + // The CLI renders a description pane beside the options. Its text may + // mention a different mode, so match only the leading option title. + const title = option.label.split(/[│┌\r\n]/, 1)[0]! + .replace(/\s+/g, '').toUpperCase(); + const mode = /^(HOLDSCOPE|SCOPEEXPANSION|SELECTIVEEXPANSION|SCOPEREDUCTION)(?:$|[^A-Z])/.exec(title)?.[1]; + return { index: option.index, mode }; + }); + if (!modes.some(option => option.mode)) return null; + + const target = modes.find(option => option.mode === targetMode.replace(/\s+/g, '')); + if (!target) { + throw new Error( + `Mode AskUserQuestion rendered but target "${targetMode}" not in option labels:\n` + + options.map(option => ` ${option.index}. ${option.label}`).join('\n'), + ); + } + return target.index; +} diff --git a/test/helpers/touchfiles-data.ts b/test/helpers/touchfiles-data.ts index 98f526879..3d609a6be 100644 --- a/test/helpers/touchfiles-data.ts +++ b/test/helpers/touchfiles-data.ts @@ -139,7 +139,7 @@ export const E2E_TOUCHFILES: Record = { // Each one tests behavior the SDK harness can't observe (rendered TTY, // numbered-option lists, multi-phase ordering, idempotency state echo). 'auq-format-gate': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/auq-sdk-capture.ts', 'test/helpers/session-runner.ts', 'test/helpers/llm-judge.ts', 'test/skill-e2e-ask-user-question-format-compliance.test.ts'], - 'plan-ceo-mode-routing': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-plan-ceo-mode-routing.test.ts'], + 'plan-ceo-mode-routing': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/helpers/ceo-mode-option.ts', 'test/ceo-mode-option.test.ts', 'test/skill-e2e-plan-ceo-mode-routing.test.ts'], 'plan-design-with-ui-scope': ['plan-design-review/**', 'test/fixtures/plans/ui-heavy-feature.md', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-plan-design-with-ui.test.ts'], 'ship-idempotency-pty': ['ship/**', 'bin/gstack-next-version', 'bin/gstack-version-bump', 'scripts/resolvers/sections.ts', 'lib/worktree.ts', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-ship-idempotency.test.ts'], 'tpa-present': ['scripts/resolvers/third-party-actions.ts', 'ship/SKILL.md.tmpl', 'ship/sections/apple-release.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/helpers/session-runner.ts', 'test/skill-e2e-third-party-actions.test.ts'], diff --git a/test/skill-e2e-plan-ceo-mode-routing.test.ts b/test/skill-e2e-plan-ceo-mode-routing.test.ts index ff113278c..a27c61007 100644 --- a/test/skill-e2e-plan-ceo-mode-routing.test.ts +++ b/test/skill-e2e-plan-ceo-mode-routing.test.ts @@ -39,11 +39,11 @@ import { isPermissionDialogVisible, parseNumberedOptions, isPlanReadyVisible, - MODE_RE, optionsSignature, TAIL_SCAN_BYTES, type ClaudePtySession, } from './helpers/claude-pty-runner'; +import { findCeoModeOption } from './helpers/ceo-mode-option'; const describeE2E = describeE2ETier('periodic'); @@ -103,15 +103,9 @@ async function navigateToModeAskUserQuestion( lastSeenList = opts; // Is THIS the mode AskUserQuestion? - if (opts.some(o => MODE_RE.test(o.label))) { - const target = opts.find(o => o.label.toUpperCase().includes(targetMode)); - if (!target) { - throw new Error( - `Mode AskUserQuestion rendered but target "${targetMode}" not in option labels:\n` + - opts.map(o => ` ${o.index}. ${o.label}`).join('\n'), - ); - } - return { modeIndex: target.index, visibleAtMode: visible }; + const modeIndex = findCeoModeOption(opts, targetMode); + if (modeIndex !== null) { + return { modeIndex, visibleAtMode: visible }; } // Permission dialog? Grant with "1" but don't count it against nav budget.