mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
d06f08938f
Part 3 of 4 (plan: ~/.claude/plans/system-instruction-you-are-working-polymorphic-twilight.md). Gate-tier (E1, free, runs on every `bun test`): test/preamble-compose.test.ts — pins the composition order Asserts AskUserQuestion Format section renders BEFORE Model-Specific Behavioral Patch in tier-≥2 preamble output. Covers claude default, opus-4-7 overlay, tier 2/3, and codex host. Catches any future edit to scripts/resolvers/preamble.ts that silently reverts the order. test/resolver-ask-user-format.test.ts — pins the Pros/Cons contract 14 assertions against generateAskUserFormat output: D<N>, ELI10, Stakes if we pick wrong:, Recommendation: <choice>, Pros / cons:, ✅/❌ markers, min 2 pros + 1 con rules, hard-stop escape exact phrase, neutral-posture CT1 rule ((recommended) label preserved for AUTO_DECIDE), Completeness coverage-vs-kind, tool_use mandate (rule 11), self-check list, D-numbering model-level caveat. test/model-overlay-opus-4-7.test.ts — pins the pacing directive Asserts raw overlay file + resolved overlay output contain "Pace questions to the skill" and NOT "Batch your questions". Verifies INHERIT:claude chain still works (Todo-list, subordination wrapper), Fan out / Effort-match / Literal interpretation nudges preserved. Also asserts claude base overlay does NOT carry the Opus-specific pacing directive (no cross-contamination). Periodic-tier (E2, Opus-dependent, ~$1-2/run): test/skill-e2e-plan-prosons.test.ts — 4 cases extending v1.6.3.0 harness 1. Format positive — every token present when plan has real tradeoff 2. Hard-stop NEGATIVE — plan with genuine tradeoff must NOT dodge to "No cons — hard-stop choice" escape 3. Neutral-posture NEGATIVE — plan where one option dominates must emit (recommended) label + "because <reason>", must NOT dodge to "taste call" / "no preference" 4. Hard-stop POSITIVE — destructive-action plan may legitimately use the hard-stop escape test/helpers/touchfiles.ts — entries for all new eval cases Dependencies: overlay, preamble.ts, generate-ask-user-format.ts, and the 4 plan-review templates. Diff-based selection triggers the evals whenever those files change. Also added entries for 7 expanded-coverage cases (ship, office-hours, investigate, qa, review, design-review, document-release) — test cases will land in follow-up PRs per skill. Follow-ups noted in test file header: - True multi-turn cadence eval (3 findings → 3 distinct asks) — current harness captures one $OUT_FILE per session; multi-turn capture needs new harness support. - Expanded-coverage test cases for the 7 non-plan-review skills. Verified: - bun test: 349 pass (30 new + 319 baseline), 1 pre-existing security-bench oversize failure on main (unrelated, unchanged). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
/**
|
|
* AskUserQuestion Format resolver — gate-tier assertions on the generated
|
|
* Pros/Cons format directive block.
|
|
*
|
|
* v1.7.0.0 introduces Pros/Cons decision-brief formatting:
|
|
* - D<N> numbered header
|
|
* - ELI10 paragraph
|
|
* - Stakes-if-we-pick-wrong line
|
|
* - Recommendation line (mandatory, even for neutral posture)
|
|
* - Pros/Cons block with ✅/❌ per option, min 2 pros + 1 con, ≥40 char bullets
|
|
* - Net: synthesis line
|
|
*
|
|
* This test pins the format contract so a future edit to the resolver
|
|
* can't silently drop a rule. If the resolver stops emitting one of
|
|
* these tokens, bun test catches it in milliseconds instead of waiting
|
|
* for the weekly periodic eval to notice.
|
|
*/
|
|
import { describe, test, expect } from 'bun:test';
|
|
import type { TemplateContext } from '../scripts/resolvers/types';
|
|
import { HOST_PATHS } from '../scripts/resolvers/types';
|
|
import { generateAskUserFormat } from '../scripts/resolvers/preamble/generate-ask-user-format';
|
|
|
|
function makeCtx(): TemplateContext {
|
|
return {
|
|
skillName: 'test-skill',
|
|
tmplPath: 'test.tmpl',
|
|
host: 'claude',
|
|
paths: HOST_PATHS.claude,
|
|
preambleTier: 2,
|
|
};
|
|
}
|
|
|
|
describe('generateAskUserFormat — v1.7.0.0 Pros/Cons format', () => {
|
|
const out = generateAskUserFormat(makeCtx());
|
|
|
|
test('includes AskUserQuestion Format header', () => {
|
|
expect(out).toContain('## AskUserQuestion Format');
|
|
});
|
|
|
|
test('documents D-numbered header requirement', () => {
|
|
expect(out).toContain('D<N>');
|
|
expect(out).toMatch(/first question in a skill invocation is `D1`/i);
|
|
});
|
|
|
|
test('documents ELI10 requirement', () => {
|
|
expect(out).toContain('ELI10');
|
|
expect(out).toMatch(/plain English.*16-year-old/);
|
|
});
|
|
|
|
test('documents Stakes-if-we-pick-wrong line', () => {
|
|
expect(out).toContain('Stakes if we pick wrong');
|
|
});
|
|
|
|
test('documents mandatory Recommendation line', () => {
|
|
expect(out).toContain('Recommendation: <choice>');
|
|
expect(out).toMatch(/Recommendation.*ALWAYS|Recommendation \(ALWAYS\)/);
|
|
});
|
|
|
|
test('documents Pros / cons block header', () => {
|
|
expect(out).toContain('Pros / cons:');
|
|
});
|
|
|
|
test('documents ✅ pro markers with min count + min length rule', () => {
|
|
expect(out).toContain('✅');
|
|
expect(out).toMatch(/[Mm]inimum 2 pros/);
|
|
expect(out).toMatch(/40 characters|≥40 chars/);
|
|
});
|
|
|
|
test('documents ❌ con markers with min count rule', () => {
|
|
expect(out).toContain('❌');
|
|
expect(out).toMatch(/1 con per option|minimum.*1 con/i);
|
|
});
|
|
|
|
test('documents hard-stop escape with exact phrase', () => {
|
|
// "No cons — this is a hard-stop choice" may span a line break in the
|
|
// rendered resolver text; match across whitespace collapses.
|
|
expect(out).toMatch(/No cons\s+—\s+this is a\s+hard-stop choice/);
|
|
});
|
|
|
|
test('documents neutral-posture escape preserving (recommended) label', () => {
|
|
// CT1 resolution: (recommended) label STAYS on default option to preserve
|
|
// AUTO_DECIDE contract. Neutrality expressed in prose only.
|
|
expect(out).toMatch(/taste call/i);
|
|
// `s` flag makes . match newlines — the label + STAYS phrase spans a line break
|
|
expect(out).toMatch(/\(recommended\)[\s\S]*STAYS|STAYS[\s\S]*\(recommended\)/);
|
|
expect(out).toMatch(/AUTO_DECIDE/);
|
|
});
|
|
|
|
test('documents Net line for closing synthesis', () => {
|
|
expect(out).toMatch(/^Net:/m);
|
|
expect(out).toMatch(/synthesis|tradeoff/i);
|
|
});
|
|
|
|
test('documents Completeness scoring rules (coverage vs kind)', () => {
|
|
expect(out).toContain('Completeness');
|
|
expect(out).toMatch(/10 = complete/);
|
|
expect(out).toMatch(/options differ in kind, not coverage/);
|
|
});
|
|
|
|
test('documents tool_use mandate (rule 11)', () => {
|
|
expect(out).toMatch(/tool_use/);
|
|
// "not a question" spans a newline in the rendered text
|
|
expect(out).toMatch(/not a[\s\S]*question|not[\s\S]*interactive/i);
|
|
});
|
|
|
|
test('includes self-check before emitting', () => {
|
|
expect(out).toContain('Self-check before emitting');
|
|
expect(out).toMatch(/D<N> header present/);
|
|
expect(out).toMatch(/Net line closes/);
|
|
});
|
|
|
|
test('documents D-numbering as model-level not runtime state', () => {
|
|
// Codex finding #4 caveat: D-numbering is a prompt wish, not a system
|
|
// guarantee. TemplateContext has no counter. This check pins the caveat.
|
|
expect(out).toMatch(/model-level instruction|not a runtime counter|count your own/i);
|
|
});
|
|
|
|
test('per-skill override guidance preserved', () => {
|
|
expect(out).toMatch(/Per-skill instructions may add/);
|
|
});
|
|
});
|