import { describe, it, expect } from "vitest"; import { buildDebugIssuePrompt } from "../../src/prompts/debug-issue.js"; describe("buildDebugIssuePrompt", () => { // --- Basic output contract --- it("returns a non-empty string given question-only input", () => { const result = buildDebugIssuePrompt({ question: "Why is the API returning 500?" }); expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); }); it("is deterministic — same input always produces the same output", () => { const a = buildDebugIssuePrompt({ question: "Why is the API returning 500?" }); const b = buildDebugIssuePrompt({ question: "Why is the API returning 500?" }); expect(a).toBe(b); }); // --- Composition with base prompt --- it("includes the base system prompt content", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("second-opinion assistant"); expect(result).toContain("Claude Code"); expect(result).toContain("not responsible for editing files"); }); it("contains a '---' separator between base and tool-specific sections", () => { const result = buildDebugIssuePrompt({ question: "Test" }); const parts = result.split("\n---\n"); expect(parts.length).toBe(2); }); // --- debug_issue-specific role instructions --- it("contains debugger/advisor role instruction", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("debugger/advisor"); }); it("instructs Claude Code as the implementation agent", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("Claude Code will implement your suggestions"); }); it("instructs ChatGPT that you do not implement anything yourself", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("you do not implement anything yourself"); }); // --- Claude Code / debugger guard rails --- it("states Claude Code remains the implementation agent", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("Claude Code remains the implementation agent"); }); it("states ChatGPT is acting only as a debugger/advisor", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("acting only as a debugger/advisor"); }); it("instructs to use only supplied code, logs, and context", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("use only the code, logs, and context provided"); }); it("states ChatGPT does not have repository-wide visibility", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("does not have repository-wide visibility"); }); // --- Investigation dimensions --- it("contains instruction to diagnose using only supplied evidence", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result.toLowerCase()).toContain("diagnose the reported issue using only"); }); it("contains instruction to identify likely root causes with confidence levels", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("most likely root causes"); expect(result.toLowerCase()).toContain("confidence levels"); }); it("contains instruction to distinguish evidence from assumptions", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result.toLowerCase()).toContain("distinguish evidence from assumptions"); }); it("contains instruction to highlight missing information", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toMatch(/highlight\s+missing\s+information/i); }); it("contains instruction to suggest focused debugging steps", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result.toLowerCase()).toContain("suggest focused debugging steps"); }); it("contains instruction to suggest fixes only when supported by evidence", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toMatch(/fixes?\s+only\s+when.*supported\s+by.*evidence/i); }); // --- Response structure (TASKS.md §7) --- it("requests Summary section in response structure", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toMatch(/summary/i); }); it("requests Likely Causes section with confidence levels in response structure", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("Likely Causes"); expect(result.toLowerCase()).toContain("confidence"); }); it("requests Fast Checks section in response structure", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("Fast Checks"); }); it("requests Minimal Safe Experiments section in response structure", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("Minimal Safe Experiments"); }); it("requests What Not To Do Yet section in response structure", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).toContain("What Not To Do Yet"); }); // --- Question field --- it("appends the input question verbatim in the task section", () => { const result = buildDebugIssuePrompt({ question: "Why does the API return 500?" }); expect(result).toContain("Question: Why does the API return 500?"); }); // --- Optional fields appended correctly (with question) --- it("appends Context when context is provided", () => { const result = buildDebugIssuePrompt({ question: "Why is the API returning 500?", context: "The issue appeared after deploying v2.3.", }); expect(result).toContain("Context: The issue appeared after deploying v2.3."); }); it("does NOT append Context section when context is empty", () => { const result = buildDebugIssuePrompt({ question: "Test", context: "" }); expect(result).not.toContain("Context:"); }); it("appends Constraints list when constraints are provided", () => { const result = buildDebugIssuePrompt({ question: "Why is the API returning 500?", constraints: ["Do not change the database schema", "Must be safe to deploy"], }); expect(result).toContain("Constraints:"); expect(result).toContain("- Do not change the database schema"); expect(result).toContain("- Must be safe to deploy"); }); it("does NOT append Constraints section when constraints array is empty", () => { const result = buildDebugIssuePrompt({ question: "Test", constraints: [] }); expect(result).not.toContain("Constraints:"); }); it("appends Expected output when expectedOutput is provided", () => { const result = buildDebugIssuePrompt({ question: "Why is the API returning 500?", expectedOutput: "A root-cause analysis with confidence levels.", }); expect(result).toContain( "Expected output: A root-cause analysis with confidence levels." ); }); it("does NOT append Expected output section when expectedOutput is omitted", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).not.toContain("Expected output:"); }); it("does NOT append Expected output section when expectedOutput is empty", () => { const result = buildDebugIssuePrompt({ question: "Test", expectedOutput: "" }); expect(result).not.toContain("Expected output:"); }); it("appends all optional fields alongside each other", () => { const result = buildDebugIssuePrompt({ question: "Why is the API returning 500?", context: "The issue appeared after deploying v2.3.", constraints: ["Do not change the database schema", "Must be safe to deploy"], expectedOutput: "A root-cause analysis with confidence levels.", projectSummary: "E-commerce platform using Express + PostgreSQL.", taskSummary: "Stripe webhook handler processes recurring payment events.", }); expect(result).toContain("Question: Why is the API returning 500?"); expect(result).toContain("Context: The issue appeared after deploying v2.3."); expect(result).toContain("Constraints:"); expect(result).toContain("- Do not change the database schema"); expect(result).toContain("- Must be safe to deploy"); expect(result).toContain("Expected output: A root-cause analysis with confidence levels."); expect(result).toContain("Project summary: E-commerce platform using Express + PostgreSQL."); expect(result).toContain( "Task summary: Stripe webhook handler processes recurring payment events." ); }); it("does NOT append Project summary when projectSummary is omitted", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).not.toContain("Project summary:"); }); it("does NOT append Task summary when taskSummary is omitted", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).not.toContain("Task summary:"); }); // --- relevantFiles (present) --- it("includes file section header when relevantFiles is provided", () => { const result = buildDebugIssuePrompt({ question: "Why does the API return 500?", relevantFiles: [{ path: "src/webhooks/stripe.js", content: "app.post('/webhook', handler);" }], }); expect(result).toContain("Relevant files:"); }); it("includes file path, language, and content when relevantFiles has language", () => { const result = buildDebugIssuePrompt({ question: "Why does the API return 500?", relevantFiles: [ { path: "src/webhooks/stripe.js", language: "javascript", content: "app.post('/webhook', handler);" }, ], }); expect(result).toContain("File: src/webhooks/stripe.js"); expect(result).toContain("Language: javascript"); expect(result).toContain("app.post('/webhook', handler);"); }); it("includes file content without language header when language is omitted", () => { const result = buildDebugIssuePrompt({ question: "Why does the API return 500?", relevantFiles: [{ path: "src/webhooks/stripe.js", content: "app.post('/webhook', handler);" }], }); expect(result).toContain("File: src/webhooks/stripe.js"); expect(result).not.toContain("Language:"); expect(result).toContain("app.post('/webhook', handler);"); }); it("includes multiple files when relevantFiles has multiple entries", () => { const result = buildDebugIssuePrompt({ question: "Why does the API return 500?", relevantFiles: [ { path: "src/webhooks/stripe.js", language: "javascript", content: "app.post('/webhook', handler);" }, { path: "src/utils/notifications.js", language: "javascript", content: "// no tests" }, ], }); expect(result).toContain("File: src/webhooks/stripe.js"); expect(result).toContain("File: src/utils/notifications.js"); expect(result).toContain("app.post('/webhook', handler);"); expect(result).toContain("// no tests"); }); // --- relevantFiles (absent/empty) --- it("does NOT include Relevant files section when relevantFiles is omitted", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).not.toContain("Relevant files:"); }); it("does NOT include Relevant files section when relevantFiles is empty array", () => { const result = buildDebugIssuePrompt({ question: "Test", relevantFiles: [] }); expect(result).not.toContain("Relevant files:"); }); // --- logs (present) --- it("includes Logs section with content when logs is provided", () => { const result = buildDebugIssuePrompt({ question: "Why does the API return 500?", logs: "ERROR TypeError: Cannot read properties of undefined", }); expect(result).toContain("Logs:"); expect(result).toContain("ERROR TypeError: Cannot read properties of undefined"); }); it("does NOT include Logs section when logs is absent", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).not.toContain("Logs:"); }); it("does NOT include Logs section when logs is empty string", () => { const result = buildDebugIssuePrompt({ question: "Test", logs: "" }); expect(result).not.toContain("Logs:"); }); // --- Tool-specific guard --- it("does NOT contain review_code specific content", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result.toLowerCase()).not.toContain("issues by severity"); expect(result.toLowerCase()).not.toContain("suggested fixes"); }); it("does NOT contain review_plan specific content", () => { const result = buildDebugIssuePrompt({ question: "Test" }); expect(result).not.toContain("missing steps"); expect(result).not.toContain("scope creep"); }); // --- Input validation --- it("throws TypeError when input is missing, null, or undefined", () => { expect(() => buildDebugIssuePrompt()).toThrow(TypeError); expect(() => buildDebugIssuePrompt(null)).toThrow(TypeError); expect(() => buildDebugIssuePrompt(undefined)).toThrow(TypeError); }); it("throws TypeError when question is missing", () => { expect(() => buildDebugIssuePrompt({ context: "something" })).toThrow(TypeError); }); it("throws TypeError when question is empty", () => { expect(() => buildDebugIssuePrompt({ question: "" })).toThrow(TypeError); }); // --- Full integration test --- it("returns a well-formed two-part prompt with all fields, relevantFiles, and logs", () => { const result = buildDebugIssuePrompt({ question: "Why does the payment endpoint return 500 for recurring invoices?", context: "The issue appeared after deploying v2.3. Only affects Stripe webhooks with retry logic.", constraints: ["Do not change the database schema", "Must be safe to deploy during business hours"], expectedOutput: "A root-cause analysis with confidence levels and minimal debugging steps.", projectSummary: "E-commerce platform using Express + PostgreSQL, deployed on EC2.", taskSummary: "Stripe webhook handler processes recurring payment events.", relevantFiles: [ { path: "src/webhooks/stripe.js", language: "javascript", content: `app.post('/webhook/stripe', async (req, res) => {\n const sig = req.headers['stripe-signature'];\n const event = stripe.webhooks.constructEvent(req.body, sig);\n if (event.type === 'invoice.payment_failed') {\n await handlePaymentFailed(event.data.object);\n }\n res.sendStatus(200);\n});`, }, ], logs: `2024-01-15T10:23:01Z ERROR StripeWebhookHandler: Failed to process event inv_1abc at handlePaymentFailed (src/webhooks/stripe.js:14:9) at IncomingMessage. (src/server.js:87:5) TypeError: Cannot read properties of undefined (reading 'customer_email') at formatRetryNotification (src/utils/notifications.js:23:35)`, }); // Base section present expect(result).toContain("second-opinion assistant"); expect(result).toContain("Rules:"); // Separator expect(result).toMatch(/\n---\n/); // Tool-specific section present expect(result).toContain("debugger/advisor"); expect(result).toContain("Claude Code remains the implementation agent"); expect(result).toContain("repository-wide visibility"); // Investigation dimensions present expect(result.toLowerCase()).toContain("diagnose the reported issue using only"); expect(result).toContain("most likely root causes"); expect(result.toLowerCase()).toContain("distinguish evidence from assumptions"); expect(result).toMatch(/highlight\s+missing\s+information/i); expect(result.toLowerCase()).toContain("suggest focused debugging steps"); expect(result).toMatch(/fixes?\s+only\s+when.*supported\s+by.*evidence/i); // Response structure present (TASKS.md §7) expect(result).toMatch(/summary/i); expect(result).toContain("Likely Causes"); expect(result.toLowerCase()).toContain("confidence"); expect(result).toContain("Fast Checks"); expect(result).toContain("Minimal Safe Experiments"); expect(result).toContain("What Not To Do Yet"); // All fields present verbatim expect(result).toContain("Question: Why does the payment endpoint return 500 for recurring invoices?"); expect(result).toContain("Context: The issue appeared after deploying v2.3."); expect(result).toContain("Constraints:"); expect(result).toContain("- Do not change the database schema"); expect(result).toContain("- Must be safe to deploy during business hours"); expect(result).toContain("Expected output: A root-cause analysis with confidence levels and minimal debugging steps."); expect(result).toContain("Project summary: E-commerce platform using Express + PostgreSQL, deployed on EC2."); expect(result).toContain("Task summary: Stripe webhook handler processes recurring payment events."); // Relevant files present expect(result).toContain("Relevant files:"); expect(result).toContain("File: src/webhooks/stripe.js"); expect(result).toContain("Language: javascript"); expect(result).toContain("app.post('/webhook/stripe', async (req, res) => {"); // Logs present expect(result).toContain("Logs:"); expect(result).toContain("ERROR StripeWebhookHandler: Failed to process event inv_1abc"); expect(result).toContain("TypeError: Cannot read properties of undefined"); // Does not contain other tool content expect(result.toLowerCase()).not.toContain("issues by severity"); expect(result).not.toContain("missing steps"); expect(result).not.toContain("scope creep"); }); });