mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
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:
1 parent
6c0fe8517d
commit
5299cdad0c
2 files changed
+14
-6
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
Reference in new issue
Block a user