feat: add debug_issue prompt builder
This commit is contained in:
@@ -29,6 +29,7 @@ Phase 3 in progress.
|
|||||||
- Task 3.3 — ask_chatgpt prompt builder (`src/prompts/ask-chatgpt.js`, `test/prompts/ask-chatgpt.test.js`) ✅
|
- Task 3.3 — ask_chatgpt prompt builder (`src/prompts/ask-chatgpt.js`, `test/prompts/ask-chatgpt.test.js`) ✅
|
||||||
- Task 3.4 — review_plan prompt builder (`src/prompts/review-plan.js`, `test/prompts/review-plan.test.js`) ✅
|
- Task 3.4 — review_plan prompt builder (`src/prompts/review-plan.js`, `test/prompts/review-plan.test.js`) ✅
|
||||||
- Task 3.5 — review_code prompt builder (`src/prompts/review-code.js`, `test/prompts/review-code.test.js`) ✅
|
- Task 3.5 — review_code prompt builder (`src/prompts/review-code.js`, `test/prompts/review-code.test.js`) ✅
|
||||||
|
- Task 3.6 — debug_issue prompt builder (`src/prompts/debug-issue.js`, `test/prompts/debug-issue.test.js`) ✅
|
||||||
|
|
||||||
## Next Phase
|
## Next Phase
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,25 @@ Create `test/prompts/debug-issue.test.js`.
|
|||||||
Requirements:
|
Requirements:
|
||||||
- Mirror structure of ask_chatgpt, review-plan, and review-code tests: base composition, debug_issue-specific instructions, optional fields, expectedOutput, input validation, tool guard, full integration.
|
- Mirror structure of ask_chatgpt, review-plan, and review-code tests: base composition, debug_issue-specific instructions, optional fields, expectedOutput, input validation, tool guard, full integration.
|
||||||
|
|
||||||
|
Status: ✅ Complete
|
||||||
|
|
||||||
|
### Task 3.7 - architecture_review prompt builder (NEXT)
|
||||||
|
|
||||||
|
Create `src/prompts/architecture-review.js`.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Export a `buildArchitectureReviewPrompt(input)` function that returns a tool-specific prompt for the architecture_review MCP tool.
|
||||||
|
- Compose with `buildBasePrompt()` — call it internally and append the result with `---` separator.
|
||||||
|
- Instruct ChatGPT to review architecture decisions and trade-offs.
|
||||||
|
- Look for: local vs cloud trade-offs, simplicity, maintainability, operational risk, vendor lock-in, future extension paths.
|
||||||
|
- Support `relevantFiles` and `context` input fields.
|
||||||
|
- No other tool prompts yet.
|
||||||
|
|
||||||
|
Create `test/prompts/architecture-review.test.js`.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Mirror structure of ask_chatgpt, review-plan, review-code, and debug-issue tests: base composition, architecture_review-specific instructions, optional fields, expectedOutput, input validation, tool guard, full integration.
|
||||||
|
|
||||||
Status: ⬜ Pending
|
Status: ⬜ Pending
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+104
-1
@@ -1 +1,104 @@
|
|||||||
// debug_issue prompt template.
|
// Tool-specific prompt builder for debug_issue.
|
||||||
|
|
||||||
|
import { buildBasePrompt } from "./base.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a tool-specific prompt for the debug_issue MCP tool.
|
||||||
|
*
|
||||||
|
* @param {{ question: string, context?: string, constraints?: string[], expectedOutput?: string, projectSummary?: string, taskSummary?: string, relevantFiles?: Array<{path: string, content: string, language?: string}>, logs?: string }} input
|
||||||
|
* @returns {string} A two-part prompt: base system message + debug_issue user message.
|
||||||
|
*/
|
||||||
|
export function buildDebugIssuePrompt(input) {
|
||||||
|
if (!input || typeof input.question !== "string" || input.question.length === 0) {
|
||||||
|
const err = new TypeError("debug_issue requires a non-empty 'question' field.");
|
||||||
|
err.kind = "ValidationError";
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = buildBasePrompt();
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"You are acting as a debugger/advisor for Claude Code. Claude Code will implement your suggestions — you do not implement anything yourself.",
|
||||||
|
"",
|
||||||
|
"Your task: diagnose the reported issue using only the supplied evidence and provide focused debugging guidance.",
|
||||||
|
"",
|
||||||
|
"Investigate systematically across these dimensions:",
|
||||||
|
"- Diagnose the reported issue using only the supplied evidence.",
|
||||||
|
"- Identify the most likely root causes with confidence levels (high / medium / low).",
|
||||||
|
"- Distinguish evidence from assumptions — call out what is inferred vs directly observed.",
|
||||||
|
"- Highlight missing information that would improve confidence.",
|
||||||
|
"- Suggest focused debugging steps ordered by speed and safety.",
|
||||||
|
"- Suggest possible fixes only when they are supported by the provided evidence.",
|
||||||
|
'- Avoid guessing — clearly state uncertainty when evidence is insufficient.',
|
||||||
|
'- Prefer the smallest explanation that fits all the evidence (Occam\'s razor).',
|
||||||
|
"",
|
||||||
|
"Important rules:",
|
||||||
|
'- Claude Code remains the implementation agent. You are acting only as a debugger/advisor.',
|
||||||
|
'- ChatGPT does not have repository-wide visibility — use only the code, logs, and context provided.',
|
||||||
|
"- If evidence is insufficient to reach confident conclusions, state so explicitly.",
|
||||||
|
"- Do not pretend to have executed commands or tests.",
|
||||||
|
"",
|
||||||
|
"Respond in this exact structure:",
|
||||||
|
'- Summary: Brief overall diagnosis of what likely went wrong.',
|
||||||
|
"- Likely Causes: Root causes grouped by confidence (High / Medium / Low).",
|
||||||
|
"- Fast Checks: Quick verifications that can confirm or rule out hypotheses.",
|
||||||
|
"- Minimal Safe Experiments: Small, low-risk changes to validate the diagnosis.",
|
||||||
|
'- What Not To Do Yet: Things that could make things worse or waste effort.',
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
|
||||||
|
lines.push(`Question: ${input.question}`);
|
||||||
|
|
||||||
|
if (input.context) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`Context: ${input.context}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.constraints && input.constraints.length > 0) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Constraints:");
|
||||||
|
for (const c of input.constraints) {
|
||||||
|
lines.push(`- ${c}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.expectedOutput) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`Expected output: ${input.expectedOutput}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.projectSummary) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`Project summary: ${input.projectSummary}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.taskSummary) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`Task summary: ${input.taskSummary}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.relevantFiles && input.relevantFiles.length > 0) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Relevant files:");
|
||||||
|
|
||||||
|
for (const file of input.relevantFiles) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`File: ${file.path}`);
|
||||||
|
if (file.language) {
|
||||||
|
lines.push(`Language: ${file.language}`);
|
||||||
|
}
|
||||||
|
lines.push(file.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.logs && input.logs.length > 0) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Logs:");
|
||||||
|
lines.push(input.logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base + "\n" + lines.join("\n");
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,409 @@
|
|||||||
|
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.<anonymous> (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");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user