Files
gstack/test/skill-e2e-plan-ceo-plan-mode.test.ts
T
Garry Tan d46a83b2e1 test: plan-mode handshake E2E coverage and unit assertions
Adds 6 E2E test files and 8 new unit assertions to verify the plan-mode
handshake works end-to-end and stays correct under regeneration.

E2E tests (gate-tier, paid, EVALS=1 EVALS_TIER=gate):
- test/skill-e2e-plan-ceo-plan-mode.test.ts — handshake fires before any
  Write/Edit when plan-mode distinctive phrase is present; 2-option shape
  (Exit/Cancel); option A routes to ExitPlanMode cleanly
- test/skill-e2e-plan-eng-plan-mode.test.ts — same contract for plan-eng
- test/skill-e2e-plan-design-plan-mode.test.ts — same contract for
  plan-design; exercises C-cancel branch instead of A-exit
- test/skill-e2e-plan-devex-plan-mode.test.ts — same contract for plan-devex
- test/skill-e2e-plan-mode-no-op.test.ts — negative regression: handshake
  must NOT fire when distinctive phrase is absent; skill proceeds normally
  through Step 0 (REGRESSION RULE guardrail against breaking existing
  interactive-review sessions)
- test/e2e-harness-audit.test.ts — free unit test asserting every
  `interactive: true` skill has at least one canUseTool-using test file
  (prevents future drift where a skill opts in without coverage)

Shared helper test/helpers/plan-mode-handshake-helpers.ts centralizes the
canUseTool interceptor + distinctive-phrase injection so the 4 sibling
E2E tests are thin wiring (~20 LOC each) and can't drift out of sync.

Unit assertions added to test/gen-skill-docs.test.ts:
- handshake section present in all 4 Claude-generated SKILL.md files
- handshake section absent from non-interactive Claude skills (ship,
  review, qa, office-hours, codex, retro, cso)
- handshake section absent from non-Claude host outputs (.agents, etc.)
- 0C-bis STOP block present in plan-ceo-review/SKILL.md at correct
  position (between the "Present these approach options" line and
  "### 0D-prelude" header)
- handshake resolver wired BEFORE generateUpgradeCheck in preamble
  composition order

6 new gate-tier entries added to test/helpers/touchfiles.ts so any change
to the handshake resolver, preamble composition, skill templates, question
registry, one-way-door classifier, or agent-sdk-runner fires the relevant
E2E tests. test/touchfiles.test.ts updated for the new selection count
(plan-ceo-review/** now triggers 15 tests, up from 8).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 23:41:13 -07:00

41 lines
1.6 KiB
TypeScript
Raw 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.
/**
* plan-ceo-review plan-mode handshake E2E (gate tier, paid).
*
* Asserts: when /plan-ceo-review is invoked with the plan-mode distinctive
* phrase in the system reminder, the skill fires AskUserQuestion FIRST
* (before any Write or Edit), the question has exactly 2 options (A exit,
* C cancel), picking "Exit" leads to an orderly exit with no plan file
* written.
*
* Cost: ~$0.50$1.00 per run. Gated: EVALS=1 EVALS_TIER=gate.
* Depends on: scripts/resolvers/preamble/generate-plan-mode-handshake.ts,
* test/helpers/agent-sdk-runner.ts (canUseTool extension).
*/
import { describe, test, expect } from 'bun:test';
import {
runPlanModeHandshakeTest,
assertHandshakeShape,
} from './helpers/plan-mode-handshake-helpers';
const shouldRun = !!process.env.EVALS && process.env.EVALS_TIER === 'gate';
const describeE2E = shouldRun ? describe : describe.skip;
describeE2E('plan-ceo-review plan-mode handshake (gate)', () => {
test('handshake fires before any Write/Edit when plan mode is detected', async () => {
const result = await runPlanModeHandshakeTest({
skillName: 'plan-ceo-review',
answerLabel: 'Exit',
});
// Handshake must have fired at least once.
expect(result.askUserQuestions.length).toBeGreaterThanOrEqual(1);
// Critically: no Write or Edit fired before the first AskUserQuestion.
// This is the bug v1.10.2.0 fixes — plan mode used to allow silent
// plan-file writes without any interactive gate.
expect(result.writeOrEditBeforeAsk).toBe(false);
// Handshake shape: 2 options (Exit/Cancel), Option B dropped per D8.
assertHandshakeShape(result.askUserQuestions[0]!);
}, 120_000);
});