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.
This commit is contained in:
garrytan committed 2026-10-03 22:31:38 +00:00
1 parent 6c0fe8517d
commit 5299cdad0c
2 files changed
+14 -6

No files matched your search

+4 -4
View File
@@ -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('<x>', 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);
+10 -2
View File
@@ -62,7 +62,7 @@ const CONTRACT: Record<TestValueBarMode, { render: string; required: string[]; a
},
qa: {
render: bar('qa'),
required: [QUESTIONS[2], QUESTIONS[3], CARD_FORMAT, 'Put it in the 8e.5 record (/qa) or under each proposed test (/qa-only).', 'A missing upstream card never blocks'],
required: [QUESTIONS[2], QUESTIONS[3], CARD_FORMAT, 'A missing upstream card never blocks'],
absent: [QUESTIONS[0], QUESTIONS[1]],
},
audit: {
@@ -83,6 +83,14 @@ describe('test value bar render contract', () => {
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([]);