mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-01 19:25:10 +02:00
feat: test coverage gate + plan completion audit + auto-verification (v0.11.13.0) (#428)
* feat: test coverage gate + plan completion audit + auto-verification Three new gates in /ship and /review: 1. Test coverage gate: configurable thresholds (60%/80% default), hard stop below minimum with user override 2. Plan completion audit: discovers plan file, extracts actionable items, cross-references against diff, gates on NOT DONE items 3. Auto-verification: invokes /qa-only inline with plan's verification section, conditional on localhost reachability Also: coverage warning in /review, plan completion data in /retro, shared plan file discovery helper (DRY), ship metrics logging. * chore: regenerate SKILL.md files * chore: bump version and changelog (v0.11.13.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -677,6 +677,168 @@ describe('PLAN_FILE_REVIEW_REPORT resolver', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// --- {{PLAN_COMPLETION_AUDIT}} resolver tests ---
|
||||
|
||||
describe('PLAN_COMPLETION_AUDIT placeholders', () => {
|
||||
const shipSkill = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
const reviewSkill = fs.readFileSync(path.join(ROOT, 'review', 'SKILL.md'), 'utf-8');
|
||||
|
||||
test('ship SKILL.md contains plan completion audit step', () => {
|
||||
expect(shipSkill).toContain('Plan Completion Audit');
|
||||
expect(shipSkill).toContain('Step 3.45');
|
||||
});
|
||||
|
||||
test('review SKILL.md contains plan completion in scope drift', () => {
|
||||
expect(reviewSkill).toContain('Plan File Discovery');
|
||||
expect(reviewSkill).toContain('Actionable Item Extraction');
|
||||
expect(reviewSkill).toContain('Integration with Scope Drift Detection');
|
||||
});
|
||||
|
||||
test('both modes share plan file discovery methodology', () => {
|
||||
expect(shipSkill).toContain('Plan File Discovery');
|
||||
expect(reviewSkill).toContain('Plan File Discovery');
|
||||
// Both should have conversation context first
|
||||
expect(shipSkill).toContain('Conversation context (primary)');
|
||||
expect(reviewSkill).toContain('Conversation context (primary)');
|
||||
// Both should have grep fallback
|
||||
expect(shipSkill).toContain('Content-based search (fallback)');
|
||||
expect(reviewSkill).toContain('Content-based search (fallback)');
|
||||
});
|
||||
|
||||
test('ship mode has gate logic for NOT DONE items', () => {
|
||||
expect(shipSkill).toContain('NOT DONE');
|
||||
expect(shipSkill).toContain('Stop — implement the missing items');
|
||||
expect(shipSkill).toContain('Ship anyway — defer');
|
||||
expect(shipSkill).toContain('intentionally dropped');
|
||||
});
|
||||
|
||||
test('review mode is INFORMATIONAL only', () => {
|
||||
expect(reviewSkill).toContain('INFORMATIONAL');
|
||||
expect(reviewSkill).toContain('MISSING REQUIREMENTS');
|
||||
expect(reviewSkill).toContain('SCOPE CREEP');
|
||||
});
|
||||
|
||||
test('item extraction has 50-item cap', () => {
|
||||
expect(shipSkill).toContain('at most 50 items');
|
||||
});
|
||||
|
||||
test('uses file-level traceability (not commit-level)', () => {
|
||||
expect(shipSkill).toContain('Cite the specific file');
|
||||
expect(shipSkill).not.toContain('commit-level traceability');
|
||||
});
|
||||
});
|
||||
|
||||
// --- {{PLAN_VERIFICATION_EXEC}} resolver tests ---
|
||||
|
||||
describe('PLAN_VERIFICATION_EXEC placeholder', () => {
|
||||
const shipSkill = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
|
||||
test('ship SKILL.md contains plan verification step', () => {
|
||||
expect(shipSkill).toContain('Step 3.47');
|
||||
expect(shipSkill).toContain('Plan Verification');
|
||||
});
|
||||
|
||||
test('references /qa-only invocation', () => {
|
||||
expect(shipSkill).toContain('qa-only/SKILL.md');
|
||||
expect(shipSkill).toContain('qa-only');
|
||||
});
|
||||
|
||||
test('contains localhost reachability check', () => {
|
||||
expect(shipSkill).toContain('localhost:3000');
|
||||
expect(shipSkill).toContain('NO_SERVER');
|
||||
});
|
||||
|
||||
test('skips gracefully when no verification section', () => {
|
||||
expect(shipSkill).toContain('No verification steps found in plan');
|
||||
});
|
||||
|
||||
test('skips gracefully when no dev server', () => {
|
||||
expect(shipSkill).toContain('No dev server detected');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Coverage gate tests ---
|
||||
|
||||
describe('Coverage gate in ship', () => {
|
||||
const shipSkill = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
const reviewSkill = fs.readFileSync(path.join(ROOT, 'review', 'SKILL.md'), 'utf-8');
|
||||
|
||||
test('ship SKILL.md contains coverage gate with thresholds', () => {
|
||||
expect(shipSkill).toContain('Coverage gate');
|
||||
expect(shipSkill).toContain('>= target');
|
||||
expect(shipSkill).toContain('< minimum');
|
||||
});
|
||||
|
||||
test('ship SKILL.md supports configurable thresholds via CLAUDE.md', () => {
|
||||
expect(shipSkill).toContain('## Test Coverage');
|
||||
expect(shipSkill).toContain('Minimum:');
|
||||
expect(shipSkill).toContain('Target:');
|
||||
});
|
||||
|
||||
test('coverage gate skips on parse failure (not block)', () => {
|
||||
expect(shipSkill).toContain('could not determine percentage — skipping');
|
||||
});
|
||||
|
||||
test('review SKILL.md contains coverage WARNING', () => {
|
||||
expect(reviewSkill).toContain('COVERAGE WARNING');
|
||||
expect(reviewSkill).toContain('Consider writing tests before running /ship');
|
||||
});
|
||||
|
||||
test('review coverage warning is INFORMATIONAL', () => {
|
||||
expect(reviewSkill).toContain('INFORMATIONAL');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Ship metrics logging ---
|
||||
|
||||
describe('Ship metrics logging', () => {
|
||||
const shipSkill = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
|
||||
test('ship SKILL.md contains metrics persistence step', () => {
|
||||
expect(shipSkill).toContain('Step 8.75');
|
||||
expect(shipSkill).toContain('coverage_pct');
|
||||
expect(shipSkill).toContain('plan_items_total');
|
||||
expect(shipSkill).toContain('plan_items_done');
|
||||
expect(shipSkill).toContain('verification_result');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Plan file discovery shared helper ---
|
||||
|
||||
describe('Plan file discovery shared helper', () => {
|
||||
// The shared helper should appear in ship (via PLAN_COMPLETION_AUDIT_SHIP)
|
||||
// and in review (via PLAN_COMPLETION_AUDIT_REVIEW)
|
||||
const shipSkill = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
const reviewSkill = fs.readFileSync(path.join(ROOT, 'review', 'SKILL.md'), 'utf-8');
|
||||
|
||||
test('plan file discovery appears in both ship and review', () => {
|
||||
expect(shipSkill).toContain('Plan File Discovery');
|
||||
expect(reviewSkill).toContain('Plan File Discovery');
|
||||
});
|
||||
|
||||
test('both include conversation context first', () => {
|
||||
expect(shipSkill).toContain('Conversation context (primary)');
|
||||
expect(reviewSkill).toContain('Conversation context (primary)');
|
||||
});
|
||||
|
||||
test('both include content-based fallback', () => {
|
||||
expect(shipSkill).toContain('Content-based search (fallback)');
|
||||
expect(reviewSkill).toContain('Content-based search (fallback)');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Retro plan completion ---
|
||||
|
||||
describe('Retro plan completion section', () => {
|
||||
const retroSkill = fs.readFileSync(path.join(ROOT, 'retro', 'SKILL.md'), 'utf-8');
|
||||
|
||||
test('retro SKILL.md contains plan completion section', () => {
|
||||
expect(retroSkill).toContain('### Plan Completion');
|
||||
expect(retroSkill).toContain('plan_items_total');
|
||||
expect(retroSkill).toContain('Plan Completion This Period');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Plan status footer in preamble ---
|
||||
|
||||
describe('Plan status footer in preamble', () => {
|
||||
|
||||
@@ -107,12 +107,17 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'gemini-review-findings': ['review/**', '.agents/skills/gstack-review/**', 'test/helpers/gemini-session-runner.ts', 'lib/worktree.ts'],
|
||||
|
||||
|
||||
// Coverage audit (shared fixture) + triage
|
||||
// Coverage audit (shared fixture) + triage + gates
|
||||
'ship-coverage-audit': ['ship/**', 'test/fixtures/coverage-audit-fixture.ts', 'bin/gstack-repo-mode'],
|
||||
'review-coverage-audit': ['review/**', 'test/fixtures/coverage-audit-fixture.ts'],
|
||||
'plan-eng-coverage-audit': ['plan-eng-review/**', 'test/fixtures/coverage-audit-fixture.ts'],
|
||||
'ship-triage': ['ship/**', 'bin/gstack-repo-mode'],
|
||||
|
||||
// Plan completion audit + verification
|
||||
'ship-plan-completion': ['ship/**', 'scripts/gen-skill-docs.ts'],
|
||||
'ship-plan-verification': ['ship/**', 'qa-only/**', 'scripts/gen-skill-docs.ts'],
|
||||
'review-plan-completion': ['review/**', 'scripts/gen-skill-docs.ts'],
|
||||
|
||||
// Design
|
||||
'design-consultation-core': ['design-consultation/**', 'scripts/gen-skill-docs.ts', 'test/helpers/llm-judge.ts'],
|
||||
'design-consultation-existing': ['design-consultation/**', 'scripts/gen-skill-docs.ts'],
|
||||
|
||||
Reference in New Issue
Block a user