docs: complete phase 3 prompt builders
This commit is contained in:
@@ -0,0 +1,465 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { buildArchitectureReviewPrompt } from "../../src/prompts/architecture-review.js";
|
||||
|
||||
describe("buildArchitectureReviewPrompt", () => {
|
||||
// --- Basic output contract ---
|
||||
|
||||
it("returns a non-empty string given question-only input", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Should we use a managed vector database?" });
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("is deterministic — same input always produces the same output", () => {
|
||||
const a = buildArchitectureReviewPrompt({ question: "Should we use a managed vector database?" });
|
||||
const b = buildArchitectureReviewPrompt({ question: "Should we use a managed vector database?" });
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
// --- Explicit separator ---
|
||||
|
||||
it("uses an explicit \\n---\\n separator between base and tool sections", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toContain("\n\n---\n\n");
|
||||
});
|
||||
|
||||
it("contains exactly one separator (two-part prompt)", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
const parts = result.split("\n\n---\n\n");
|
||||
expect(parts.length).toBe(2);
|
||||
});
|
||||
|
||||
// --- Composition with base prompt ---
|
||||
|
||||
it("includes the base system prompt content", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toContain("second-opinion assistant");
|
||||
expect(result).toContain("Claude Code");
|
||||
expect(result).toContain("not responsible for editing files");
|
||||
});
|
||||
|
||||
// --- Architecture advisor role instructions ---
|
||||
|
||||
it("contains architecture advisor role instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toContain("architecture advisor");
|
||||
});
|
||||
|
||||
it("instructs Claude Code as the implementation agent", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toContain("Claude Code will implement your suggestions");
|
||||
});
|
||||
|
||||
it("instructs ChatGPT that you do not implement anything yourself", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toContain("you do not implement anything yourself");
|
||||
});
|
||||
|
||||
it("states ChatGPT is acting only as an architecture advisor", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/acting\s+only\s+as\s+an?\s+architecture\s+advisor/i);
|
||||
});
|
||||
|
||||
// --- Guard rails ---
|
||||
|
||||
it("states Claude Code remains the implementation agent", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toContain("Claude Code remains the implementation agent");
|
||||
});
|
||||
|
||||
it("instructs ChatGPT to use only supplied context and files", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/use\s+only\s+the?\s+supplied\s+context/i);
|
||||
});
|
||||
|
||||
it("instructs ChatGPT not to assume repository-wide context", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/not\s+assume\s+repository-wide\s+context/i);
|
||||
});
|
||||
|
||||
it("instructs to prefer simple, incremental architecture", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/prefer\s+simple.*incremental.*architecture/i);
|
||||
});
|
||||
|
||||
it("instructs to avoid large rewrites unless justified", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/avoid\s+large\s+rewrites.*unless.*justified/i);
|
||||
});
|
||||
|
||||
// --- Review dimensions (10) ---
|
||||
|
||||
it("contains Simplicity dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("simplicity");
|
||||
});
|
||||
|
||||
it("contains Maintainability dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("maintainability");
|
||||
});
|
||||
|
||||
it("contains Security dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/security/i);
|
||||
});
|
||||
|
||||
it("contains Scalability dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("scalability");
|
||||
});
|
||||
|
||||
it("contains Operational burden dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("operational burden");
|
||||
});
|
||||
|
||||
it("contains Vendor lock-in dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("vendor lock-in");
|
||||
});
|
||||
|
||||
it("contains Local-first / self-hosted fit dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toMatch(/local-?first.*self-?hosted\s+fit|self-?hosted.*local-?first/i);
|
||||
});
|
||||
|
||||
it("contains Integration risk dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("integration risk");
|
||||
});
|
||||
|
||||
it("contains Migration risk dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("migration risk");
|
||||
});
|
||||
|
||||
it("contains Appropriateness for current project stage dimension instruction", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/appropriateness.*project\s+stage|current\s+project\s+stage/i);
|
||||
});
|
||||
|
||||
// --- Response structure (8 sections) ---
|
||||
|
||||
it("requests Summary section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/summary/i);
|
||||
});
|
||||
|
||||
it("requests Recommendation section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/recommendation/i);
|
||||
});
|
||||
|
||||
it("requests Trade-offs section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/trade-?offs?/i);
|
||||
});
|
||||
|
||||
it("requests Risks section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/risks/i);
|
||||
});
|
||||
|
||||
it("requests Future Extension Paths section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/future\s+extension\s+paths/i);
|
||||
});
|
||||
|
||||
it("requests Simpler Alternatives section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).toContain("simpler alternatives");
|
||||
});
|
||||
|
||||
it("requests Implementation Notes section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/implementation\s+notes/i);
|
||||
});
|
||||
|
||||
it("requests Decision Confidence section in response structure", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).toMatch(/decision\s+confidence/i);
|
||||
});
|
||||
|
||||
// --- Question field ---
|
||||
|
||||
it("appends the input question verbatim in the task section", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Should we migrate to S3?" });
|
||||
expect(result).toContain("Question: Should we migrate to S3?");
|
||||
});
|
||||
|
||||
// --- Optional fields appended correctly (with question) ---
|
||||
|
||||
it("appends Context when context is provided", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we use a managed vector DB?",
|
||||
context: "Current stack uses PostgreSQL with pgvector for search.",
|
||||
});
|
||||
expect(result).toContain("Context: Current stack uses PostgreSQL with pgvector for search.");
|
||||
});
|
||||
|
||||
it("does NOT append Context section when context is empty", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test", context: "" });
|
||||
expect(result).not.toContain("Context:");
|
||||
});
|
||||
|
||||
it("appends Constraints list when constraints are provided", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we use a managed vector DB?",
|
||||
constraints: ["Must be self-hostable", "Budget under $100/mo"],
|
||||
});
|
||||
expect(result).toContain("Constraints:");
|
||||
expect(result).toContain("- Must be self-hostable");
|
||||
expect(result).toContain("- Budget under $100/mo");
|
||||
});
|
||||
|
||||
it("does NOT append Constraints section when constraints array is empty", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test", constraints: [] });
|
||||
expect(result).not.toContain("Constraints:");
|
||||
});
|
||||
|
||||
it("appends Expected output when expectedOutput is provided", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we use a managed vector DB?",
|
||||
expectedOutput: "A trade-off analysis comparing options.",
|
||||
});
|
||||
expect(result).toContain(
|
||||
"Expected output: A trade-off analysis comparing options."
|
||||
);
|
||||
});
|
||||
|
||||
it("does NOT append Expected output section when expectedOutput is omitted", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).not.toContain("Expected output:");
|
||||
});
|
||||
|
||||
it("does NOT append Expected output section when expectedOutput is empty", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test", expectedOutput: "" });
|
||||
expect(result).not.toContain("Expected output:");
|
||||
});
|
||||
|
||||
it("appends all optional fields alongside each other", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we migrate to S3?",
|
||||
context: "Current stack uses local disk with Express.",
|
||||
constraints: ["Must be deployable without Kubernetes", "Budget under $100/mo"],
|
||||
expectedOutput: "A comparison of S3, MinIO, and shared NFS.",
|
||||
projectSummary: "Internal document management system for a 50-person team.",
|
||||
taskSummary: "Migrating from local filesystem to scalable object storage.",
|
||||
});
|
||||
expect(result).toContain("Question: Should we migrate to S3?");
|
||||
expect(result).toContain("Context: Current stack uses local disk with Express.");
|
||||
expect(result).toContain("Constraints:");
|
||||
expect(result).toContain("- Must be deployable without Kubernetes");
|
||||
expect(result).toContain("- Budget under $100/mo");
|
||||
expect(result).toContain("Expected output: A comparison of S3, MinIO, and shared NFS.");
|
||||
expect(result).toContain(
|
||||
"Project summary: Internal document management system for a 50-person team."
|
||||
);
|
||||
expect(result).toContain(
|
||||
"Task summary: Migrating from local filesystem to scalable object storage."
|
||||
);
|
||||
});
|
||||
|
||||
it("does NOT append Project summary when projectSummary is omitted", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).not.toContain("Project summary:");
|
||||
});
|
||||
|
||||
it("does NOT append Task summary when taskSummary is omitted", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).not.toContain("Task summary:");
|
||||
});
|
||||
|
||||
// --- relevantFiles (present) ---
|
||||
|
||||
it("includes file section header when relevantFiles is provided", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we migrate to S3?",
|
||||
relevantFiles: [
|
||||
{ path: "src/storage/local.js", content: "fs.writeFileSync(path, data);" },
|
||||
],
|
||||
});
|
||||
expect(result).toContain("Relevant files:");
|
||||
});
|
||||
|
||||
it("includes file path, language, and content when relevantFiles has language", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we migrate to S3?",
|
||||
relevantFiles: [
|
||||
{ path: "src/storage/local.js", language: "javascript", content: "fs.writeFileSync(path, data);" },
|
||||
],
|
||||
});
|
||||
expect(result).toContain("File: src/storage/local.js");
|
||||
expect(result).toContain("Language: javascript");
|
||||
expect(result).toContain("fs.writeFileSync(path, data);");
|
||||
});
|
||||
|
||||
it("includes file content without language header when language is omitted", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we migrate to S3?",
|
||||
relevantFiles: [
|
||||
{ path: "src/storage/local.js", content: "fs.writeFileSync(path, data);" },
|
||||
],
|
||||
});
|
||||
expect(result).toContain("File: src/storage/local.js");
|
||||
expect(result).not.toContain("Language:");
|
||||
expect(result).toContain("fs.writeFileSync(path, data);");
|
||||
});
|
||||
|
||||
it("includes multiple files when relevantFiles has multiple entries", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we migrate to S3?",
|
||||
relevantFiles: [
|
||||
{ path: "src/storage/local.js", language: "javascript", content: "fs.writeFileSync(path, data);" },
|
||||
{ path: "src/middleware/upload.js", language: "javascript", content: "app.post('/upload', handler);" },
|
||||
],
|
||||
});
|
||||
expect(result).toContain("File: src/storage/local.js");
|
||||
expect(result).toContain("File: src/middleware/upload.js");
|
||||
expect(result).toContain("fs.writeFileSync(path, data);");
|
||||
expect(result).toContain("app.post('/upload', handler);");
|
||||
});
|
||||
|
||||
// --- relevantFiles (absent/empty) ---
|
||||
|
||||
it("does NOT include Relevant files section when relevantFiles is omitted", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).not.toContain("Relevant files:");
|
||||
});
|
||||
|
||||
it("does NOT include Relevant files section when relevantFiles is empty array", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test", relevantFiles: [] });
|
||||
expect(result).not.toContain("Relevant files:");
|
||||
});
|
||||
|
||||
// --- Tool-specific guard ---
|
||||
|
||||
it("does NOT contain debug_issue specific content", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).not.toContain("Likely Causes");
|
||||
expect(result).not.toContain("Fast Checks");
|
||||
});
|
||||
|
||||
it("does NOT contain review_plan specific content", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result).not.toContain("missing steps");
|
||||
expect(result).not.toContain("scope creep");
|
||||
});
|
||||
|
||||
it("does NOT contain review_code specific content", () => {
|
||||
const result = buildArchitectureReviewPrompt({ question: "Test" });
|
||||
expect(result.toLowerCase()).not.toContain("issues by severity");
|
||||
expect(result.toLowerCase()).not.toContain("suggested fixes");
|
||||
});
|
||||
|
||||
// --- Input validation ---
|
||||
|
||||
it("throws TypeError when input is missing, null, or undefined", () => {
|
||||
expect(() => buildArchitectureReviewPrompt()).toThrow(TypeError);
|
||||
expect(() => buildArchitectureReviewPrompt(null)).toThrow(TypeError);
|
||||
expect(() => buildArchitectureReviewPrompt(undefined)).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it("throws TypeError when question is missing", () => {
|
||||
expect(() => buildArchitectureReviewPrompt({ context: "something" })).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it("throws TypeError when question is empty", () => {
|
||||
expect(() => buildArchitectureReviewPrompt({ question: "" })).toThrow(TypeError);
|
||||
});
|
||||
|
||||
// --- Full integration test ---
|
||||
|
||||
it("returns a well-formed two-part prompt with all fields and relevantFiles", () => {
|
||||
const result = buildArchitectureReviewPrompt({
|
||||
question: "Should we migrate our file storage from local filesystem to an object store?",
|
||||
context: "Current system stores uploads on the application server's disk. We're getting disk space alerts and need multi-server support.",
|
||||
constraints: ["Must be deployable without Kubernetes", "Cost must not exceed current $50/mo by more than 50%"],
|
||||
expectedOutput: "A comparison of S3, MinIO, and shared NFS with trade-off analysis.",
|
||||
projectSummary: "Internal document management system for a mid-sized company.",
|
||||
taskSummary: "Migrating from local disk storage to a scalable file storage solution.",
|
||||
relevantFiles: [
|
||||
{
|
||||
path: "src/storage/local-filesystem.js",
|
||||
language: "javascript",
|
||||
content: `const fs = require('fs');\n\nfunction saveFile(name, data) {\n fs.writeFileSync(\`./uploads/\${name}\`, data);\n}`,
|
||||
},
|
||||
{
|
||||
path: "src/middleware/upload.js",
|
||||
content: `app.post('/upload', upload.single('file'), (req, res) => { ... });`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Base section present
|
||||
expect(result).toContain("second-opinion assistant");
|
||||
expect(result).toContain("Rules:");
|
||||
|
||||
// Explicit separator — exactly one
|
||||
const parts = result.split("\n\n---\n\n");
|
||||
expect(parts.length).toBe(2);
|
||||
|
||||
// Tool-specific section present (second part)
|
||||
const toolSection = parts[1];
|
||||
expect(toolSection).toContain("architecture advisor");
|
||||
expect(toolSection).toContain("Claude Code remains the implementation agent");
|
||||
expect(toolSection).toMatch(/use\s+only\s+the?\s+supplied\s+context/i);
|
||||
|
||||
// Guard rails present
|
||||
expect(toolSection).toMatch(/prefer\s+simple.*incremental.*architecture/i);
|
||||
expect(toolSection).toMatch(/avoid\s+large\s+rewrites.*unless.*justified/i);
|
||||
|
||||
// All 10 review dimensions
|
||||
expect(toolSection.toLowerCase()).toContain("simplicity");
|
||||
expect(toolSection.toLowerCase()).toContain("maintainability");
|
||||
expect(toolSection).toMatch(/security/i);
|
||||
expect(toolSection.toLowerCase()).toContain("scalability");
|
||||
expect(toolSection.toLowerCase()).toContain("operational burden");
|
||||
expect(toolSection.toLowerCase()).toContain("vendor lock-in");
|
||||
expect(toolSection.toLowerCase()).toMatch(/local-?first.*self-?hosted\s+fit|self-?hosted.*local-?first/i);
|
||||
expect(toolSection.toLowerCase()).toContain("integration risk");
|
||||
expect(toolSection.toLowerCase()).toContain("migration risk");
|
||||
expect(toolSection).toMatch(/appropriateness.*project\s+stage|current\s+project\s+stage/i);
|
||||
|
||||
// All 8 response structure sections
|
||||
expect(toolSection).toMatch(/summary/i);
|
||||
expect(toolSection).toMatch(/recommendation/i);
|
||||
expect(toolSection).toMatch(/trade-?offs?/i);
|
||||
expect(toolSection).toMatch(/risks/i);
|
||||
expect(toolSection).toMatch(/future\s+extension\s+paths/i);
|
||||
expect(toolSection.toLowerCase()).toContain("simpler alternatives");
|
||||
expect(toolSection).toMatch(/implementation\s+notes/i);
|
||||
expect(toolSection).toMatch(/decision\s+confidence/i);
|
||||
|
||||
// All fields present verbatim
|
||||
expect(result).toContain("Question: Should we migrate our file storage from local filesystem to an object store?");
|
||||
expect(result).toContain("Context: Current system stores uploads on the application server's disk. We're getting disk space alerts and need multi-server support.");
|
||||
expect(result).toContain("Constraints:");
|
||||
expect(result).toContain("- Must be deployable without Kubernetes");
|
||||
expect(result).toContain("- Cost must not exceed current $50/mo by more than 50%");
|
||||
expect(result).toContain(
|
||||
"Expected output: A comparison of S3, MinIO, and shared NFS with trade-off analysis."
|
||||
);
|
||||
expect(result).toContain(
|
||||
"Project summary: Internal document management system for a mid-sized company."
|
||||
);
|
||||
expect(result).toContain(
|
||||
"Task summary: Migrating from local disk storage to a scalable file storage solution."
|
||||
);
|
||||
|
||||
// Relevant files present
|
||||
expect(toolSection).toContain("Relevant files:");
|
||||
expect(toolSection).toContain("File: src/storage/local-filesystem.js");
|
||||
expect(toolSection).toContain("Language: javascript");
|
||||
expect(toolSection).toContain("fs.writeFileSync");
|
||||
expect(toolSection).toContain("File: src/middleware/upload.js");
|
||||
|
||||
// Does not contain other tool content
|
||||
expect(toolSection).not.toContain("Likely Causes");
|
||||
expect(toolSection).not.toContain("Fast Checks");
|
||||
expect(toolSection).not.toContain("missing steps");
|
||||
expect(toolSection).not.toContain("scope creep");
|
||||
expect(toolSection.toLowerCase()).not.toContain("issues by severity");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user