From b8e626c47ea554c2b164fd01d91eaad9227ac560 Mon Sep 17 00:00:00 2001 From: Jack Felke Date: Thu, 12 Mar 2026 13:45:02 -0700 Subject: [PATCH] test: add prompt_score unit tests + fix scope scoring bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Export scorePrompt for testability - Fix: long prompts (>100 chars) no longer get max scope score (25→20) without explicit scope-bounding keywords - Add 16 tests covering all scoring dimensions, grading, and feedback - Test count: 43 → 59 --- src/tools/prompt-score.ts | 6 +- tests/tools/prompt-score.test.ts | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/tools/prompt-score.test.ts diff --git a/src/tools/prompt-score.ts b/src/tools/prompt-score.ts index 1cecf01..1468a32 100644 --- a/src/tools/prompt-score.ts +++ b/src/tools/prompt-score.ts @@ -40,7 +40,7 @@ interface ScoreResult { feedback: string[]; } -function scorePrompt(text: string): ScoreResult { +export function scorePrompt(text: string): ScoreResult { const feedback: string[] = []; let specificity: number; let scope: number; @@ -59,8 +59,10 @@ function scorePrompt(text: string): ScoreResult { } // Scope: bounded task - if (/\b(only|just|single|one|specific|this)\b/i.test(text) || text.length > 100) { + if (/\b(only|just|single|one|specific|this)\b/i.test(text)) { scope = 25; + } else if (text.length > 100) { + scope = 20; } else if (/\b(all|every|entire|whole)\b/i.test(text)) { scope = 10; feedback.push("🎯 'All/every' is broad — can you narrow the scope?"); diff --git a/tests/tools/prompt-score.test.ts b/tests/tools/prompt-score.test.ts new file mode 100644 index 0000000..65648f2 --- /dev/null +++ b/tests/tools/prompt-score.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from "vitest"; +import { scorePrompt } from "../../src/tools/prompt-score.js"; + +describe("scorePrompt", () => { + it("gives high specificity for file paths", () => { + const result = scorePrompt("Fix the bug in src/tools/prompt-score.ts"); + expect(result.specificity).toBe(25); + }); + + it("gives high specificity for backtick identifiers", () => { + const result = scorePrompt("Rename `scorePrompt` to `evaluatePrompt`"); + expect(result.specificity).toBe(25); + }); + + it("gives medium specificity for generic terms", () => { + const result = scorePrompt("Fix the component"); + expect(result.specificity).toBe(15); + }); + + it("gives low specificity for vague prompts", () => { + const result = scorePrompt("Make it work"); + expect(result.specificity).toBe(5); + }); + + it("gives high scope for bounded keywords", () => { + const result = scorePrompt("Only update the return type"); + expect(result.actionability).toBeGreaterThan(0); + expect(result.scope).toBe(25); + }); + + it("gives medium scope for long prompts without scope keywords", () => { + const longPrompt = "I need you to look at the authentication flow and figure out why the tokens are expiring too quickly and causing users to get logged out repeatedly during their session"; + const result = scorePrompt(longPrompt); + expect(result.scope).toBe(20); + }); + + it("penalizes unbounded scope keywords", () => { + const result = scorePrompt("Fix all the bugs"); + expect(result.scope).toBe(10); + }); + + it("gives high actionability for specific verbs", () => { + const result = scorePrompt("Extract the validation logic into a helper"); + expect(result.actionability).toBe(25); + }); + + it("gives medium actionability for vague verbs", () => { + const result = scorePrompt("Make the login page work better"); + expect(result.actionability).toBe(15); + }); + + it("gives high done-condition for outcome words", () => { + const result = scorePrompt("Fix the test so it should return 200"); + expect(result.doneCondition).toBe(25); + }); + + it("gives decent done-condition for questions", () => { + const result = scorePrompt("Why is the build failing?"); + expect(result.doneCondition).toBe(20); + }); + + it("grades A+ for excellent prompts", () => { + const result = scorePrompt( + "Rename only the `validateToken` function in src/auth/tokens.ts — it should return a Result type instead of throwing" + ); + expect(result.total).toBeGreaterThanOrEqual(90); + expect(result.grade).toBe("A+"); + }); + + it("grades poorly for vague prompts", () => { + const result = scorePrompt("do stuff"); + expect(result.total).toBeLessThan(45); + expect(result.grade).toBe("F"); + }); + + it("returns feedback suggestions for weak areas", () => { + const result = scorePrompt("do stuff"); + expect(result.feedback.length).toBeGreaterThan(0); + expect(result.feedback.some(f => f.includes("📁"))).toBe(true); + }); + + it("returns praise for excellent prompts", () => { + const result = scorePrompt( + "Rename only the `validateToken` function in src/auth/tokens.ts — it should return a Result type" + ); + expect(result.feedback.some(f => f.includes("🏆"))).toBe(true); + }); + + it("total is sum of all dimensions", () => { + const result = scorePrompt("Fix the bug in src/app.ts"); + expect(result.total).toBe( + result.specificity + result.scope + result.actionability + result.doneCondition + ); + }); +});