mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 08:54:04 +01:00
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.
This commit is contained in:
1 parent
bb2cc54140
commit
db72f47498
4 files changed
+101
-11
No files matched your search
@@ -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']);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
// 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'],
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user