From 4132aa45523f097b4b11f43762e0739b868a765d Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 11 Jun 2026 14:17:42 +0100 Subject: [PATCH] feat: add base prompt template --- src/prompts/base.js | 75 ++++++++++++- test/prompts/base.test.js | 231 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 test/prompts/base.test.js diff --git a/src/prompts/base.js b/src/prompts/base.js index 2eb003c..86291b8 100644 --- a/src/prompts/base.js +++ b/src/prompts/base.js @@ -1 +1,74 @@ -// Base prompt template. +// Base system prompt builder for ChatGPT advisory use. + +/** + * Build a base system prompt for ChatGPT advisory use. + * + * @param {{ question?: string, context?: string, constraints?: string[], projectSummary?: string, taskSummary?: string }} [input] + * @returns {string} The complete base prompt as a single string. + */ +export function buildBasePrompt(input) { + const lines = [ + "You are ChatGPT acting as a second-opinion assistant for Claude Code.", + + "Claude Code is the primary coding agent and is currently backed by a local Ollama coding model.", + + "You are not responsible for editing files, running commands, deploying, committing code, or making autonomous decisions.", + + "Your role:", + "- Review plans", + "- Review code snippets", + "- Analyse architecture", + "- Debug issues", + "- Identify risks", + "- Suggest safer implementation approaches", + "- Break work into smaller steps", + + "Rules:", + "- Be concise and actionable.", + "- Use only the context provided.", + "- Do not assume access to the full repository.", + "- Do not request secrets.", + "- Do not recommend sending entire repositories.", + "- Prefer small reviewable changes.", + "- Prefer simple architecture.", + "- Prefer local/self-hosted options where practical.", + "- Call out uncertainty clearly.", + "- Avoid over-engineering.", + '- Do not pretend to have executed code or tests.', + ]; + + const question = input?.question; + + if (question) { + lines.push(""); + lines.push("---"); + lines.push("Your task:"); + lines.push(""); + lines.push(`Question: ${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?.projectSummary) { + lines.push(""); + lines.push(`Project summary: ${input.projectSummary}`); + } + + if (input?.taskSummary) { + lines.push(""); + lines.push(`Task summary: ${input.taskSummary}`); + } + } + + return lines.join("\n"); +} diff --git a/test/prompts/base.test.js b/test/prompts/base.test.js new file mode 100644 index 0000000..d9dc2d3 --- /dev/null +++ b/test/prompts/base.test.js @@ -0,0 +1,231 @@ +import { describe, it, expect } from "vitest"; +import { buildBasePrompt } from "../../src/prompts/base.js"; + +describe("buildBasePrompt", () => { + // --- Basic output contract --- + + it("returns a non-empty string when called with no arguments", () => { + const result = buildBasePrompt(); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + it("returns a non-empty string when called with an empty object", () => { + const result = buildBasePrompt({}); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + it("is idempotent — same output on repeated calls with no input", () => { + const a = buildBasePrompt(); + const b = buildBasePrompt(); + expect(a).toBe(b); + }); + + // --- Role concepts --- + + it("mentions ChatGPT as a second-opinion assistant for Claude Code", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("second-opinion"); + expect(prompt).toContain("Claude Code"); + }); + + it("states ChatGPT should not modify files", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("files"); + }); + + it("contains the concept of not running commands", () => { + const prompt = buildBasePrompt(); + expect(prompt).toMatch(/commands?\s*(?:,|\.)/); + }); + + it("contains the concept of not deploying", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("deploying"); + }); + + it("contains the concept of not making autonomous decisions", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("decisions"); + }); + + // --- Reviewer / planner / debugger / architecture advisor / risk checker concepts --- + + it("includes reviewer role (review plans or code)", () => { + const prompt = buildBasePrompt(); + expect(prompt.toLowerCase()).toContain("review"); + expect(prompt.toLowerCase()).toContain("code"); + }); + + it("includes debugger role (debug issues)", () => { + const prompt = buildBasePrompt(); + expect(prompt).toMatch(/debug\s+issues?/i); + }); + + it("includes architecture advisor role (analyse architecture)", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("architecture"); + }); + + it("includes risk checker role (identify risks)", () => { + const prompt = buildBasePrompt(); + expect(prompt).toMatch(/risks?/); + }); + + // --- Project preferences --- + + it("reflects preference for simple architecture", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("simple"); + expect(prompt).toContain("architecture"); + }); + + it("reflects preference for small changes", () => { + const prompt = buildBasePrompt(); + expect(prompt).toMatch(/small/i); + }); + + it("reflects local-first approach (prefer local options)", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("local"); + }); + + it("reflects security-conscious stance (do not request secrets)", () => { + const prompt = buildBasePrompt(); + expect(prompt).toContain("secrets"); + }); + + // --- "Your task" section gating --- + + it("does NOT emit 'Your task' section when question is absent", () => { + const result = buildBasePrompt({ context: "some context" }); + expect(result).not.toContain("Your task"); + }); + + it("does NOT emit 'Your task' section when question is an empty string", () => { + const result = buildBasePrompt({ question: "" }); + expect(result).not.toContain("Your task"); + }); + + it("emits 'Your task' section when question is present and non-empty", () => { + const result = buildBasePrompt({ question: "How should I handle auth?" }); + expect(result).toContain("Your task"); + }); + + it("emits 'Your task' only when question is provided, ignoring other fields alone", () => { + const result = buildBasePrompt({ + context: "some context", + constraints: ["must be safe"], + projectSummary: "a project", + taskSummary: "a task", + }); + expect(result).not.toContain("Your task"); + }); + + // --- Input fields appended correctly (requires question) --- + + it("appends Question when question is provided", () => { + const result = buildBasePrompt({ question: "What should I do?" }); + expect(result).toContain("Question: What should I do?"); + }); + + it("appends Context when context is provided alongside question", () => { + const result = buildBasePrompt({ + question: "Is this OK?", + context: "This is the relevant code.", + }); + expect(result).toContain("Context: This is the relevant code."); + }); + + it("does NOT append Context section when context is empty alongside question", () => { + const result = buildBasePrompt({ question: "Is this OK?", context: "" }); + expect(result).not.toContain("Context:"); + }); + + it("appends Constraints list when constraints are provided alongside question", () => { + const result = buildBasePrompt({ + question: "Review my plan", + 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 = buildBasePrompt({ question: "Review my plan", constraints: [] }); + expect(result).not.toContain("Constraints:"); + }); + + it("appends Project summary when projectSummary is provided alongside question", () => { + const result = buildBasePrompt({ + question: "What should I do?", + projectSummary: "A small Node.js API.", + }); + expect(result).toContain("Project summary: A small Node.js API."); + }); + + it("does NOT append Project summary when projectSummary is empty", () => { + const result = buildBasePrompt({ question: "What should I do?", projectSummary: "" }); + expect(result).not.toContain("Project summary:"); + }); + + it("appends Task summary when taskSummary is provided alongside question", () => { + const result = buildBasePrompt({ + question: "What should I do?", + taskSummary: "Adding rate limiting.", + }); + expect(result).toContain("Task summary: Adding rate limiting."); + }); + + it("does NOT append Task summary when taskSummary is empty", () => { + const result = buildBasePrompt({ question: "What should I do?", taskSummary: "" }); + expect(result).not.toContain("Task summary:"); + }); + + // --- No tool-specific content --- + + it("does NOT contain tool-specific prompt identifiers", () => { + const prompt = buildBasePrompt(); + expect(prompt).not.toContain("review-plan"); + expect(prompt).not.toContain("review_code"); + expect(prompt).not.toContain("debug-issue"); + expect(prompt).not.toContain("architecture-review"); + }); + + // --- Full input test --- + + it("includes all sections when all fields are provided", () => { + const result = buildBasePrompt({ + question: "Is this approach correct?", + context: "Current handler catches all errors.", + constraints: ["no stack traces", "no raw data"], + projectSummary: "Private VPC REST API.", + taskSummary: "Adding rate limiting.", + }); + + expect(result).toContain("Your task"); + expect(result).toContain("Question: Is this approach correct?"); + expect(result).toContain("Context: Current handler catches all errors."); + expect(result).toContain("Constraints:"); + expect(result).toContain("- no stack traces"); + expect(result).toContain("- no raw data"); + expect(result).toContain("Project summary: Private VPC REST API."); + expect(result).toContain("Task summary: Adding rate limiting."); + }); + + it("returns the base prompt (without task section) when question is undefined but other fields exist", () => { + const result = buildBasePrompt({ + context: "some context", + constraints: ["rule 1"], + projectSummary: "a summary", + taskSummary: "a task", + }); + + expect(result).not.toContain("Your task"); + expect(result).not.toContain("Question:"); + expect(result).not.toContain("Context: some context"); + expect(result).not.toContain("Constraints:"); + }); +});