feat: add ask_chatgpt prompt builder

This commit is contained in:
2026-06-11 15:37:42 +01:00
parent 4132aa4552
commit 008f38e630
4 changed files with 328 additions and 2 deletions
+3 -1
View File
@@ -25,7 +25,9 @@ Phase 3 in progress.
- Task 2.2 — Response builder (`src/openai/responses.js`) ✅ - Task 2.2 — Response builder (`src/openai/responses.js`) ✅
- Task 2.3 — Error handling and edge cases for OpenAI integration (tests) ✅ - Task 2.3 — Error handling and edge cases for OpenAI integration (tests) ✅
- Task 3.1 — Zod input validation schemas (`src/tools/schemas.js`, `test/tools/schemas.test.js`) ✅ - Task 3.1 — Zod input validation schemas (`src/tools/schemas.js`, `test/tools/schemas.test.js`) ✅
- Task 3.2 — Base prompt template (`src/prompts/base.js`, `test/prompts/base.test.js`) ✅
- Task 3.3 — ask_chatgpt prompt builder (`src/prompts/ask-chatgpt.js`, `test/prompts/ask-chatgpt.test.js`) ✅
## Next Phase ## Next Phase
Phase 3 — in progress; next is Task 3.2 (Base prompt template). Phase 3 — in progress; next is Task 3.4 (review_plan prompt builder).
+39
View File
@@ -172,6 +172,45 @@ Requirements:
- Test that the prompt is a non-empty string. - Test that the prompt is a non-empty string.
- Test that the prompt does not include tool-specific content (review/code/debug/architecture). - Test that the prompt does not include tool-specific content (review/code/debug/architecture).
### Task 3.3 - ask_chatgpt prompt builder
Create `src/prompts/ask-chatgpt.js`.
Requirements:
- Export a `buildAskChatGptPrompt(input)` function that returns a tool-specific prompt for the ask_chatgpt MCP tool.
- Compose with `buildBasePrompt()` — call it internally and append the result with `---` separator.
- The final prompt has two parts: [base system prompt] + [ask_chatgpt user instructions].
- Instruct ChatGPT to answer the question directly, act as a second-opinion advisor, identify assumptions, identify risks, suggest safer approaches, state uncertainty clearly.
- Support `expectedOutput` — if provided, append "Expected output: <value>" to the task section.
- Question is required; throw TypeError when missing or empty.
- No review_plan, review_code, debug_issue, or architecture_review content yet.
Create `test/prompts/ask-chatgpt.test.js`.
Requirements:
- ~27 tests covering base composition, ask_chatgpt instructions, optional field append/omit, expectedOutput present/absent, tool-specific guard, input validation, and full integration.
- No MCP code. No OpenAI calls. Pure prompt string assertions only.
Status: ✅ Complete
### Task 3.4 - review_plan prompt builder (NEXT)
Create `src/prompts/review-plan.js`.
Requirements:
- Export a `buildReviewPlanPrompt(input)` function that returns a tool-specific prompt for the review_plan MCP tool.
- Compose with `buildBasePrompt()` — call it internally and append the result with `---` separator.
- Instruct ChatGPT to review a proposed implementation plan before Claude Code acts.
- Look for: missing steps, unsafe assumptions, scope creep, better sequencing, test gaps.
- No other tool prompts yet (debug_issue, architecture_review, etc.).
Create `test/prompts/review-plan.test.js`.
Requirements:
- Mirror structure of ask-chatgpt tests: base composition, review_plan-specific instructions, optional fields, expectedOutput, input validation, tool guard, full integration.
Status: ⬜ Pending
--- ---
Phase 2 complete. Phase 3 in progress. Phase 2 complete. Phase 3 in progress.
+70 -1
View File
@@ -1 +1,70 @@
// ask_chatgpt prompt template. // Tool-specific prompt builder for ask_chatgpt.
import { buildBasePrompt } from "./base.js";
/**
* Build a tool-specific prompt for the ask_chatgpt MCP tool.
*
* @param {{ question: string, context?: string, constraints?: string[], expectedOutput?: string, projectSummary?: string, taskSummary?: string }} input
* @returns {string} A two-part prompt: base system message + ask-chatgpt user message.
*/
export function buildAskChatGptPrompt(input) {
if (!input || typeof input.question !== "string" || input.question.length === 0) {
const err = new TypeError("ask_chatgpt requires a non-empty 'question' field.");
err.kind = "ValidationError";
throw err;
}
const base = buildBasePrompt();
const lines = [
"",
"---",
"",
"You are acting as a general second-opinion advisor for Claude Code. Claude Code will implement your suggestions — you do not implement anything yourself.",
"",
"Your task: answer the question directly, then provide brief reasoning.",
"",
"When responding:",
"- Answer the question directly first.",
"- Act as a second-opinion advisor only.",
"- Identify key assumptions in the question or context.",
"- Identify risks or edge cases others might miss.",
"- Suggest safer approaches when appropriate.",
"- State uncertainty clearly — do not guess.",
'- Prefer small, reviewable changes over large rewrites.',
"",
];
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}`);
}
return base + "\n" + lines.join("\n");
}
+216
View File
@@ -0,0 +1,216 @@
import { describe, it, expect } from "vitest";
import { buildAskChatGptPrompt } from "../../src/prompts/ask-chatgpt.js";
describe("buildAskChatGptPrompt", () => {
// --- Basic output contract ---
it("returns a non-empty string given question-only input", () => {
const result = buildAskChatGptPrompt({ question: "Is this OK?" });
expect(typeof result).toBe("string");
expect(result.length).toBeGreaterThan(0);
});
it("is deterministic — same input always produces the same output", () => {
const a = buildAskChatGptPrompt({ question: "Is this OK?" });
const b = buildAskChatGptPrompt({ question: "Is this OK?" });
expect(a).toBe(b);
});
// --- Composition with base prompt ---
it("includes the base system prompt content", () => {
const result = buildAskChatGptPrompt({ 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 = buildAskChatGptPrompt({ question: "Test" });
const parts = result.split("\n---\n");
expect(parts.length).toBe(2);
});
// --- ask_chatgpt-specific role instructions ---
it("contains ask_chatgpt-specific role instructions (second-opinion advisor)", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result.toLowerCase()).toContain("second-opinion advisor");
});
it("instructs ChatGPT to answer the question directly", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result).toMatch(/answer\s+the\s+question\s+direct/i);
});
it("includes section about identifying assumptions", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result.toLowerCase()).toContain("assumptions");
});
it("includes section about identifying risks", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result.toLowerCase()).toContain("risks");
});
it("includes section about suggesting safer approaches", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result).toMatch(/safer\s+approaches?/i);
});
it("includes section about stating uncertainty clearly", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result.toLowerCase()).toContain("uncertainty");
});
// --- Question field ---
it("appends the input question verbatim in the task section", () => {
const result = buildAskChatGptPrompt({ question: "What auth method should I use?" });
expect(result).toContain("Question: What auth method should I use?");
});
// --- Optional fields appended correctly (with question) ---
it("appends Context when context is provided", () => {
const result = buildAskChatGptPrompt({
question: "Review this",
context: "The code catches all errors.",
});
expect(result).toContain("Context: The code catches all errors.");
});
it("does NOT append Context section when context is empty", () => {
const result = buildAskChatGptPrompt({ question: "Review this", context: "" });
expect(result).not.toContain("Context:");
});
it("appends Constraints list when constraints are provided", () => {
const result = buildAskChatGptPrompt({
question: "Review this",
constraints: ["must be safe", "must be fast"],
});
expect(result).toContain("Constraints:");
expect(result).toContain("- must be safe");
expect(result).toContain("- must be fast");
});
it("does NOT append Constraints section when constraints array is empty", () => {
const result = buildAskChatGptPrompt({ question: "Review this", constraints: [] });
expect(result).not.toContain("Constraints:");
});
// --- expectedOutput (approved test additions) ---
it("appends Expected output when expectedOutput is provided", () => {
const result = buildAskChatGptPrompt({
question: "Polling vs webhooks?",
expectedOutput: "A comparison table with pros/cons and a recommendation.",
});
expect(result).toContain("Expected output: A comparison table with pros/cons and a recommendation.");
});
it("does NOT append Expected output section when expectedOutput is omitted", () => {
const result = buildAskChatGptPrompt({ question: "Polling vs webhooks?" });
expect(result).not.toContain("Expected output:");
});
it("does NOT append Expected output section when expectedOutput is empty", () => {
const result = buildAskChatGptPrompt({ question: "Polling vs webhooks?", expectedOutput: "" });
expect(result).not.toContain("Expected output:");
});
it("appends all optional fields alongside expectedOutput", () => {
const result = buildAskChatGptPrompt({
question: "Should I use JWT or sessions?",
context: "Current API uses token auth.",
constraints: ["no external deps"],
expectedOutput: "A one-paragraph recommendation.",
projectSummary: "Internal inventory tool.",
taskSummary: "Migrating authentication.",
});
expect(result).toContain("Question: Should I use JWT or sessions?");
expect(result).toContain("Context: Current API uses token auth.");
expect(result).toContain("Constraints:");
expect(result).toContain("- no external deps");
expect(result).toContain("Expected output: A one-paragraph recommendation.");
expect(result).toContain("Project summary: Internal inventory tool.");
expect(result).toContain("Task summary: Migrating authentication.");
});
it("does NOT append Project summary when projectSummary is omitted", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result).not.toContain("Project summary:");
});
it("does NOT append Task summary when taskSummary is omitted", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result).not.toContain("Task summary:");
});
// --- Tool-specific guard ---
it("does NOT contain review_plan specific content", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result).not.toContain("review-plan");
expect(result).not.toContain("missing steps");
expect(result).not.toContain("scope creep");
});
it("does NOT contain review_code specific content", () => {
const result = buildAskChatGptPrompt({ question: "Test" });
expect(result).not.toContain("review_code");
expect(result).not.toContain("patch");
expect(result).not.toContain("diff");
});
// --- Input validation ---
it("throws TypeError when input is missing", () => {
expect(() => buildAskChatGptPrompt()).toThrow(TypeError);
expect(() => buildAskChatGptPrompt(null)).toThrow(TypeError);
});
it("throws TypeError when question is missing", () => {
expect(() => buildAskChatGptPrompt({ context: "something" })).toThrow(TypeError);
});
it("throws TypeError when question is empty", () => {
expect(() => buildAskChatGptPrompt({ question: "" })).toThrow(TypeError);
});
// --- Full integration test ---
it("returns a well-formed two-part prompt with all fields", () => {
const result = buildAskChatGptPrompt({
question: "Is this approach safe?",
context: "We log errors to console.error.",
constraints: ["no stack traces", "no raw data"],
expectedOutput: "A concise risk assessment.",
projectSummary: "Private VPC REST API.",
taskSummary: "Hardening error logging.",
});
// 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).toMatch(/answer\s+the\s+question\s+direct/i);
expect(result).toContain("assumptions");
expect(result).toContain("risks");
expect(result).toContain("safer approaches");
expect(result).toContain("uncertainty");
// All fields present verbatim
expect(result).toContain("Question: Is this approach safe?");
expect(result).toContain("Context: We log errors to console.error.");
expect(result).toContain("- no stack traces");
expect(result).toContain("Expected output: A concise risk assessment.");
expect(result).toContain("Project summary: Private VPC REST API.");
expect(result).toContain("Task summary: Hardening error logging.");
});
});