mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
touchfiles.test.ts now checks, per key, that the paid file's static test/helpers and test/fixtures closure (plus fixture paths it names in string literals) is covered, and names the file, path, chain and key to fix when it is not. Free *.test.ts files are no longer touchfiles, so editing a free replay test stops selecting paid evals: 950 entries removed, 653 real closure paths added. The hand-copied inventories go: periodic-fixture-selection, fake-impeccable-touchfiles and 45 per-file selection examples. Selection for the sample edits (plan-eng-review template, claude-pty-runner, plan-count-fixture, gstack-config) loses no case under either profile. CONTRIBUTING documents the rule and its lower bound.
121 lines
7.4 KiB
TypeScript
121 lines
7.4 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { findCeoModeOption, hasNativePostAnswerCeoPosture } from './helpers/ceo-mode-option';
|
|
import { parseNumberedOptions } from './helpers/claude-pty-runner';
|
|
import { readPlanCountTranscript, type NativePublicToolEvent } from './helpers/plan-count-transcript';
|
|
import captured from './fixtures/ceo-expansion-auq-ac.json';
|
|
|
|
const posture = /\b(expansion|10x|delight|dream|cathedral|opt[\s-]?in)\b/i;
|
|
const selectedAt = Date.parse(captured.provenance.testSelectionLowerBound.at);
|
|
type Records = typeof captured.records;
|
|
|
|
function replay(change?: (records: Records) => void) {
|
|
const records = structuredClone(captured.records);
|
|
change?.(records);
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ceo-expansion-auq-'));
|
|
const first = records[0]!;
|
|
const project = path.join(root, 'projects', 'fixture');
|
|
fs.mkdirSync(project, { recursive: true });
|
|
fs.writeFileSync(path.join(project, `${first.sessionId}.jsonl`),
|
|
records.map(record => JSON.stringify(record)).join('\n') + '\n');
|
|
const events: NativePublicToolEvent[] = [];
|
|
try {
|
|
return { transcript: readPlanCountTranscript(root, first.cwd, event => events.push(event)), events };
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function matches(evidence = replay()) {
|
|
return hasNativePostAnswerCeoPosture(evidence.transcript, 'SCOPE EXPANSION', posture, selectedAt, evidence.events);
|
|
}
|
|
|
|
describe('CEO expansion posture in a completed native decision brief', () => {
|
|
test('captured option 2 and subsequent answered expansion establish posture without standalone prose', () => {
|
|
const evidence = replay();
|
|
expect(findCeoModeOption(parseNumberedOptions(captured.visibleAtModeTail), 'SCOPE EXPANSION')).toBe(2);
|
|
expect(evidence.transcript.calls.map(call => call.toolUseId)).toEqual([
|
|
captured.modeToolUseId, captured.expansionToolUseId,
|
|
]);
|
|
expect(evidence.transcript.assistantMessages.every(message => Date.parse(message.timestamp) < selectedAt)).toBe(true);
|
|
expect(evidence.events.map(event => [event.toolUseId, event.timestamp])).toEqual([
|
|
[captured.modeToolUseId, captured.requestReplyTimes[captured.modeToolUseId].request],
|
|
[captured.modeToolUseId, captured.requestReplyTimes[captured.modeToolUseId].reply],
|
|
[captured.expansionToolUseId, captured.requestReplyTimes[captured.expansionToolUseId].request],
|
|
[captured.expansionToolUseId, captured.requestReplyTimes[captured.expansionToolUseId].reply],
|
|
]);
|
|
expect(matches(evidence)).toBe(true);
|
|
// Without the public native request/reply timestamps, no new evidence is inferred.
|
|
expect(hasNativePostAnswerCeoPosture(evidence.transcript, 'SCOPE EXPANSION', posture, selectedAt)).toBe(false);
|
|
});
|
|
|
|
test.each([
|
|
'wrong selected mode', 'pending selected mode', 'failed selected mode', 'selected answer before selection',
|
|
'pending expansion', 'failed expansion', 'unrecognized expansion answer', 'foreign session',
|
|
'pre-mode request', 'request after reply', 'reply timestamp mismatch', 'missing request', 'missing reply',
|
|
'failed public reply', 'wrong tool name', 'foreign tool identity', 'conflicting request', 'conflicting reply',
|
|
'request metadata mismatch', 'batched questions', 'multiselect', 'setup heading', 'quoted brief',
|
|
'fenced brief', 'quoted expansion marker', 'wrong expansion choices',
|
|
])('%s cannot supply posture coverage', failure => {
|
|
const evidence = replay();
|
|
const [mode, expansion] = evidence.transcript.calls;
|
|
const request = evidence.events[2]!;
|
|
const reply = evidence.events[3]!;
|
|
const question = expansion!.questions[0]!;
|
|
switch (failure) {
|
|
case 'wrong selected mode': mode!.answers![mode!.questions[0]!.question] = 'HOLD SCOPE'; break;
|
|
case 'pending selected mode': mode!.answered = false; break;
|
|
case 'failed selected mode': mode!.failed = true; break;
|
|
case 'selected answer before selection': mode!.answeredAt = '2026-09-09T16:40:00.000Z'; break;
|
|
case 'pending expansion': expansion!.answered = false; break;
|
|
case 'failed expansion': expansion!.failed = true; break;
|
|
case 'unrecognized expansion answer': expansion!.answers![question.question] = 'invented approval'; break;
|
|
case 'foreign session': expansion!.sessionId = request.sessionId = reply.sessionId = 'unrelated-session'; break;
|
|
case 'pre-mode request': request.timestamp = captured.requestReplyTimes[captured.modeToolUseId].request; break;
|
|
case 'request after reply': request.timestamp = '2026-09-09T16:41:22.000Z'; break;
|
|
case 'reply timestamp mismatch': reply.timestamp = '2026-09-09T16:41:22.000Z'; break;
|
|
case 'missing request': evidence.events.splice(2, 1); break;
|
|
case 'missing reply': evidence.events.splice(3, 1); break;
|
|
case 'failed public reply': reply.isError = true; break;
|
|
case 'wrong tool name': request.name = 'Read'; break;
|
|
case 'foreign tool identity': request.toolUseId = 'unrelated-call'; break;
|
|
case 'conflicting request': evidence.events.push({ ...request, timestamp: '2026-09-09T16:41:20.000Z' }); break;
|
|
case 'conflicting reply': evidence.events.push({ ...reply, isError: true }); break;
|
|
case 'request metadata mismatch': request.input = { questions: [] }; break;
|
|
case 'batched questions': expansion!.questions.push(structuredClone(question)); break;
|
|
case 'multiselect': question.multiSelect = true; break;
|
|
case 'setup heading': question.question = question.question.replace(/^D5[^\n]+/, 'D5 — Choose the review mode?'); break;
|
|
case 'quoted brief': question.question = question.question.split('\n').map(line => `> ${line}`).join('\n'); break;
|
|
case 'fenced brief': question.question = '```text\n' + question.question + '\n```'; break;
|
|
case 'quoted expansion marker': question.question = 'D5 — Which choice?\n> Expansion 1 of 8: scope opt-in'; break;
|
|
case 'wrong expansion choices': question.options[1]!.label = 'Enable telemetry'; break;
|
|
}
|
|
// Keep parsed call metadata bound to the public request. This makes the
|
|
// content negatives exercise semantic guards, not an accidental mismatch.
|
|
if (['batched questions', 'multiselect', 'setup heading', 'quoted brief', 'fenced brief',
|
|
'quoted expansion marker', 'wrong expansion choices'].includes(failure)) {
|
|
request.input = { questions: expansion!.questions };
|
|
expansion!.answers = { [question.question]: question.options[0]!.label };
|
|
}
|
|
expect(matches(evidence)).toBe(false);
|
|
});
|
|
|
|
test('the original caller regex and mode remain required', () => {
|
|
const { transcript, events } = replay();
|
|
expect(hasNativePostAnswerCeoPosture(transcript, 'SCOPE EXPANSION', /\bcathedral\b/i, selectedAt, events)).toBe(false);
|
|
expect(hasNativePostAnswerCeoPosture(transcript, 'HOLD SCOPE', /\bhold\s*scope\b/i, selectedAt, events)).toBe(false);
|
|
expect(hasNativePostAnswerCeoPosture(transcript, 'SCOPE EXPANSION', posture, Date.now(), events)).toBe(false);
|
|
});
|
|
|
|
test('reader-level foreign, sidechain, incomplete and failed records cannot create native evidence', () => {
|
|
for (const change of [
|
|
(records: Records) => { records[5]!.cwd = '/unrelated'; },
|
|
(records: Records) => { records[5]!.isSidechain = true; },
|
|
(records: Records) => { records.splice(6, 1); },
|
|
(records: Records) => { records[6]!.message.content[0]!.is_error = true; },
|
|
]) expect(matches(replay(change))).toBe(false);
|
|
});
|
|
});
|