From 5299cdad0cf89cd06626b5237d69321c44ea6e5e Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 3 Oct 2026 22:31:38 +0000 Subject: [PATCH] I3: /qa-only's value-card rule names only its own card location qa-only rendered the /qa line 'Put it in the 8e.5 record (/qa) or under each proposed test (/qa-only)'; judge samples for 'qa-only/SKILL.md workflow' (actionability at the 4.0 gate in run 37151477069, 3.67 in 36907895813) flag the /qa-only-irrelevant 8e.5 record. The qa-mode bar now renders the invoking skill's location; both skills get shorter. --- scripts/resolvers/test-value.ts | 8 ++++---- test/test-value-bar.test.ts | 12 ++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/resolvers/test-value.ts b/scripts/resolvers/test-value.ts index 4379ecbf3..d627c8057 100644 --- a/scripts/resolvers/test-value.ts +++ b/scripts/resolvers/test-value.ts @@ -122,11 +122,11 @@ function questionList(mode: TestValueBarMode): string { return questions.map((question, index) => `${index + 1}. ${question}`).join('\n'); } -function cardRules(mode: TestValueBarMode): string { +function cardRules(mode: TestValueBarMode, skillName: string): string { const where = { plan: 'One card per Critical Path and Edge Case in the Test Plan Artifact.', ship: 'Write it as a header comment in each generated test, next to the attribution (wrap, do not truncate); with no known comment syntax, put it in the PR body\'s Test value details.', - qa: 'Put it in the 8e.5 record (/qa) or under each proposed test (/qa-only).', + qa: skillName === 'qa-only' ? 'Put it under each proposed test.' : 'Put it in the 8e.5 record.', audit: 'Read cards from test header comments when present.', }[mode]; return `Value card: \`Value: protects=<...>; fails_when=<...>; why_new=<...>; seam=none\` (seam: \`none\` or its name); each field at most ${CARD_FIELD_MAX_BYTES} UTF-8 bytes here (clamp to 157 plus \`...\`; JSON keeps full values). ${where} A missing upstream card never blocks: derive it; ignore unknown fields. @@ -150,13 +150,13 @@ Retention bar: keep a test that independently enforces a public API, protocol, c Retirement card, complete before any edit: ${RETIREMENT_FIELDS.map(field => `\`${field}\``).join(', ')}. Caller check for a symbol matching \`${CALLER_SYMBOL_PATTERN}\` (otherwise "caller check unavailable: unsupported symbol"): \`${CALLER_SEARCH_COMMAND}\`; record the command, exclusions and hit count. The evidence is grep-only (no re-exports, dynamic dispatch or generated code), so production code is retired only when the repo's typecheck/build or dead-code tool passes with it removed in a scratch worktree.`; } -export function generateTestValueBar(_ctx: TemplateContext, args?: string[]): string { +export function generateTestValueBar(ctx: TemplateContext, args?: string[]): string { const mode = args?.[0] as TestValueBarMode; if (!TEST_VALUE_BAR_MODES.includes(mode)) throw new Error(MESSAGES.unknownMode.message.replace('', String(args?.[0]))); const parts = [ `**Test value bar.** ${mode === 'qa' ? 'Before writing or proposing a test, the reproduced bug already answers what it protects and what makes it fail; also answer:' : 'Propose or write a test only with all four answers; otherwise extend an existing test or drop it:'}`, questionList(mode), - cardRules(mode), + cardRules(mode, ctx.skillName), ]; if (mode !== 'qa') parts.splice(2, 0, 'A test that breaks under a behavior-preserving refactor asserts implementation: rewrite it at the owning boundary, unless exact output is the declared contract (goldens, prompt bytes, wire formats).'); if (mode === 'plan') parts.push(`${STAR_RULE} /ship computes them; here every proposed test needs a card.`, RETENTION_ONE_LINER); diff --git a/test/test-value-bar.test.ts b/test/test-value-bar.test.ts index d115ed184..284c84c49 100644 --- a/test/test-value-bar.test.ts +++ b/test/test-value-bar.test.ts @@ -62,7 +62,7 @@ const CONTRACT: Record { expect(Buffer.byteLength(rendered, 'utf8')).toBeLessThanOrEqual(TEST_VALUE_BAR_MAX_BYTES[mode]); }); + test('qa mode names only the invoking skill\'s card location (/qa-only has no 8e.5 record)', () => { + const forSkill = (skillName: string) => generateTestValueBar({ ...ctx, skillName }, ['qa']); + expect(forSkill('qa')).toContain('Put it in the 8e.5 record.'); + expect(forSkill('qa')).not.toContain('proposed test'); + expect(forSkill('qa-only')).toContain('Put it under each proposed test.'); + expect(forSkill('qa-only')).not.toContain('8e.5'); + }); + test('plan and ship coverage audits render the bar at their call site', () => { for (const mode of ['plan', 'ship'] as const) expect(generateTestCoverageAuditInner(ctx, mode)).toContain(bar(mode)); }); @@ -187,7 +195,7 @@ describe('template contracts', () => { expect(read('qa/SKILL.md.tmpl')).toContain('{{TEST_VALUE_BAR:qa}}'); expect(read('qa-only/SKILL.md.tmpl')).toContain('{{TEST_VALUE_BAR:qa}}'); expect(read('test-audit/SKILL.md.tmpl')).toContain('{{TEST_VALUE_BAR:audit}}'); - for (const skill of ['qa', 'qa-only']) expect(read(`${skill}/SKILL.md`), `${skill} lacks the rendered bar. ${FIX}`).toContain(bar('qa')); + for (const skill of ['qa', 'qa-only']) expect(read(`${skill}/SKILL.md`), `${skill} lacks the rendered bar. ${FIX}`).toContain(generateTestValueBar({ ...ctx, skillName: skill }, ['qa'])); expect(read('test-audit/SKILL.md')).toContain(bar('audit')); const templates = spawnSync('git', ['ls-files', '*.tmpl'], { cwd: ROOT, encoding: 'utf8', timeout: 10_000 }).stdout.split('\n').filter(Boolean); expect(templates.filter(file => read(file).includes('prefer too many to too few'))).toEqual([]);