mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-31 18:30:39 +02:00
Six paid PTY tests wrote their expected plan artifact to a FIXED shared
/tmp path ('/tmp/gstack-test-plan-<mode>.md') and rmSync'd it in
finally — under --retry 1, EVALS_JOBS>1, or two concurrent worktrees, a
sibling's cleanup deletes this run's artifact and the D19 'agent did
not produce expected plan file' assertion fires spuriously. Each test
now mkdtemps its own dir, interpolates the unique path into the agent
prompt (fixture-sourced prompts get a replaceAll + drift guard that
throws if the fixture's literal ever moves), and cleans up its own dir.
The 18 cwd:-into-the-live-repo sites were audited: all deliberate
(skill registry + hermetic pre-trusted dir, in-repo gen renders, git
history reads, slug resolution) — each now carries a
'// LIVE-REPO CWD: <reason>' comment so the next audit can tell
deliberate from accidental.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
138 lines
5.2 KiB
TypeScript
138 lines
5.2 KiB
TypeScript
/**
|
|
* /plan-devex-review per-finding AskUserQuestion count (periodic, paid, real-PTY).
|
|
*
|
|
* Same shape as skill-e2e-plan-ceo-finding-count: drives /plan-devex-review
|
|
* against a 5-finding seeded plan and asserts review-phase AUQ count ∈ [N-1, N+2].
|
|
* Plus D19: review report at bottom of produced plan file.
|
|
*
|
|
* Tier: periodic (~25 min, ~$5/run). Sequential by default per plan §D15.
|
|
*/
|
|
|
|
import { test } from 'bun:test';
|
|
import { describeE2ETier } from './helpers/e2e-gate';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import {
|
|
runPlanSkillCounting,
|
|
devexStep0Boundary,
|
|
assertReviewReportAtBottom,
|
|
} from './helpers/claude-pty-runner';
|
|
|
|
const describeE2E = describeE2ETier('periodic');
|
|
|
|
const N = 5;
|
|
const FLOOR = N - 1;
|
|
const CEILING = N + 2;
|
|
|
|
const planDevex5Findings = (planPath: string) => [
|
|
`Please review this plan thoroughly. As you go, write your plan-mode plan to ${planPath} (use Edit/Write to that exact path).`,
|
|
'',
|
|
'# Plan: Public SDK Beta Launch',
|
|
'',
|
|
'## Persona',
|
|
"The plan doesn't specify which developer persona is the target — we're",
|
|
"shipping for \"everyone,\" which means we tune for nobody.",
|
|
'',
|
|
'## TTHW (time to hello world)',
|
|
'Time-to-hello-world is not measured. No benchmark data referenced. We',
|
|
"don't know if first-run takes 5 minutes or 50.",
|
|
'',
|
|
'## Friction Point',
|
|
'First-run currently requires a 5-minute mandatory CI step before the',
|
|
'developer can run their first eval. There is no way to skip it.',
|
|
'',
|
|
'## Magical Moment',
|
|
'Getting-started flow has no delight beat. Pure documentation, no',
|
|
'interactive demo, no "ah-ha" moment that makes the developer trust us.',
|
|
'',
|
|
'## Competitive Blind Spot',
|
|
"The plan doesn't reference how peer SDKs (LangChain, Semantic Kernel,",
|
|
'OpenAI) handle this DX surface. We may be reinventing worse versions',
|
|
'of solved problems.',
|
|
].join('\n');
|
|
|
|
describeE2E('/plan-devex-review per-finding AskUserQuestion count (periodic)', () => {
|
|
test(
|
|
`5-finding plan emits ${FLOOR}-${CEILING} review-phase AskUserQuestions`,
|
|
async () => {
|
|
// Per-run artifact dir: a hardcoded shared /tmp path collides under
|
|
// --retry, EVALS_JOBS>1, or concurrent worktrees (a sibling's finally-
|
|
// rmSync deletes this run's artifact → spurious D19 failure).
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-e2e-plan-devex-'));
|
|
const planPath = path.join(tmpDir, 'gstack-test-plan-devex.md');
|
|
|
|
try {
|
|
const obs = await runPlanSkillCounting({
|
|
skillName: 'plan-devex-review',
|
|
slashCommand: '/plan-devex-review',
|
|
followUpPrompt: planDevex5Findings(planPath),
|
|
isLastStep0AUQ: devexStep0Boundary,
|
|
reviewCountCeiling: CEILING + 1,
|
|
// LIVE-REPO CWD: PTY session needs the repo cwd — gstack skill
|
|
// registry + hermetic pre-trusted dir (hermetic-env trustedDirs).
|
|
cwd: process.cwd(),
|
|
timeoutMs: 1_500_000,
|
|
env: { QUESTION_TUNING: 'false', EXPLAIN_LEVEL: 'default' },
|
|
});
|
|
|
|
if (!['plan_ready', 'completion_summary', 'ceiling_reached'].includes(obs.outcome)) {
|
|
throw new Error(
|
|
`plan-devex-review finding-count FAILED: outcome=${obs.outcome}\n` +
|
|
`step0=${obs.step0Count} review=${obs.reviewCount} elapsed=${obs.elapsedMs}ms\n` +
|
|
`fingerprints (last 8):\n` +
|
|
obs.fingerprints
|
|
.slice(-8)
|
|
.map(
|
|
(f, i) =>
|
|
` ${i}. preReview=${f.preReview} sig=${f.signature.slice(0, 12)} prompt="${f.promptSnippet.slice(0, 60)}"`,
|
|
)
|
|
.join('\n') +
|
|
`\n--- evidence (last 3KB) ---\n${obs.evidence}`,
|
|
);
|
|
}
|
|
if (obs.reviewCount < FLOOR) {
|
|
throw new Error(
|
|
`BAND FAIL (below floor): reviewCount=${obs.reviewCount} < FLOOR=${FLOOR}.\n` +
|
|
`Likely batching regression. Review-phase fingerprints:\n` +
|
|
obs.fingerprints
|
|
.filter((f) => !f.preReview)
|
|
.map((f) => ` - "${f.promptSnippet.slice(0, 80)}"`)
|
|
.join('\n'),
|
|
);
|
|
}
|
|
if (obs.reviewCount > CEILING) {
|
|
throw new Error(
|
|
`BAND FAIL (above ceiling): reviewCount=${obs.reviewCount} > CEILING=${CEILING}.`,
|
|
);
|
|
}
|
|
|
|
if (!fs.existsSync(planPath)) {
|
|
throw new Error(
|
|
`D19 FAIL: agent did not produce expected plan file at ${planPath}. ` +
|
|
`outcome=${obs.outcome} review=${obs.reviewCount}`,
|
|
);
|
|
}
|
|
const planContent = fs.readFileSync(planPath, 'utf-8');
|
|
const verdict = assertReviewReportAtBottom(planContent);
|
|
if (!verdict.ok) {
|
|
throw new Error(
|
|
`D19 FAIL: plan file at ${planPath} ${verdict.reason}\n` +
|
|
(verdict.trailingHeadings
|
|
? `Trailing headings: ${verdict.trailingHeadings.join(' | ')}\n`
|
|
: '') +
|
|
`--- plan content (last 1KB) ---\n${planContent.slice(-1024)}`,
|
|
);
|
|
}
|
|
} finally {
|
|
try {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
} catch {
|
|
/* best-effort */
|
|
}
|
|
}
|
|
},
|
|
1_700_000,
|
|
);
|
|
});
|