From 876648080eff6bfb6bd246c56fe36c8a8e26d08f Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 5 May 2026 18:14:03 -0700 Subject: [PATCH] fix: embed prd-creator structure in roadmap slicer --- .../__tests__/templates/slicer-prompt.test.ts | 197 ++++++++++-------- packages/core/src/templates/slicer-prompt.ts | 154 +++++++++++--- templates/slicer.md | 3 + 3 files changed, 238 insertions(+), 116 deletions(-) diff --git a/packages/core/src/__tests__/templates/slicer-prompt.test.ts b/packages/core/src/__tests__/templates/slicer-prompt.test.ts index 551e1125..ed2f4600 100644 --- a/packages/core/src/__tests__/templates/slicer-prompt.test.ts +++ b/packages/core/src/__tests__/templates/slicer-prompt.test.ts @@ -2,141 +2,166 @@ * Tests for the Slicer Prompt Template rendering system */ -import { describe, it, expect, beforeEach } from "vitest"; +import { describe, it, expect, beforeEach } from 'vitest'; import { renderSlicerPrompt, loadSlicerTemplate, clearTemplateCache, createSlicerPromptVars, ISlicerPromptVars, -} from "../../templates/slicer-prompt.js"; +} from '../../templates/slicer-prompt.js'; function createTestVars(overrides: Partial = {}): ISlicerPromptVars { return { - title: "Test Feature", - section: "Features", - description: "A test feature description", - outputFilePath: "/path/to/prds/01-test-feature.md", - prdDir: "/path/to/prds", + title: 'Test Feature', + section: 'Features', + description: 'A test feature description', + outputFilePath: '/path/to/prds/01-test-feature.md', + prdDir: '/path/to/prds', ...overrides, }; } -describe("slicer-prompt", () => { +describe('slicer-prompt', () => { beforeEach(() => { // Clear the template cache before each test clearTemplateCache(); }); - describe("renderSlicerPrompt", () => { - it("should interpolate all placeholders", () => { + describe('renderSlicerPrompt', () => { + it('should interpolate all placeholders', () => { const vars = createTestVars({ - title: "My Awesome Feature", - section: "Roadmap", - description: "This feature does amazing things", - outputFilePath: "/project/docs/PRDs/42-awesome.md", - prdDir: "/project/docs/PRDs", + title: 'My Awesome Feature', + section: 'Roadmap', + description: 'This feature does amazing things', + outputFilePath: '/project/docs/PRDs/42-awesome.md', + prdDir: '/project/docs/PRDs', }); const result = renderSlicerPrompt(vars); - expect(result).toContain("My Awesome Feature"); - expect(result).toContain("Roadmap"); - expect(result).toContain("This feature does amazing things"); - expect(result).toContain("/project/docs/PRDs/42-awesome.md"); - expect(result).toContain("/project/docs/PRDs"); + expect(result).toContain('My Awesome Feature'); + expect(result).toContain('Roadmap'); + expect(result).toContain('This feature does amazing things'); + expect(result).toContain('/project/docs/PRDs/42-awesome.md'); + expect(result).toContain('/project/docs/PRDs'); }); - it("should not contain any uninterpolated placeholders", () => { + it('should not contain any uninterpolated placeholders', () => { const vars = createTestVars(); const result = renderSlicerPrompt(vars); - expect(result).not.toContain("{{"); - expect(result).not.toContain("}}"); + expect(result).not.toContain('{{'); + expect(result).not.toContain('}}'); }); - it("should include prd-creator template structure", () => { + it('should include prd-creator template structure', () => { const vars = createTestVars(); const result = renderSlicerPrompt(vars); // Check for key PRD structure elements - expect(result).toContain("Complexity"); - expect(result).toContain("Execution Phases"); - expect(result).toContain("Context"); - expect(result).toContain("Solution"); - expect(result).toContain("Acceptance Criteria"); + expect(result).toContain('Complexity'); + expect(result).toContain('Execution Phases'); + expect(result).toContain('Context'); + expect(result).toContain('Solution'); + expect(result).toContain('Acceptance Criteria'); }); - it("should include complexity scoring guide", () => { + it('should embed prd-creator instructions instead of referencing an external file', () => { const vars = createTestVars(); const result = renderSlicerPrompt(vars); - expect(result).toContain("Complexity Scoring"); - expect(result).toContain("Touches 1-5 files"); - expect(result).toContain("LOW"); - expect(result).toContain("MEDIUM"); - expect(result).toContain("HIGH"); + expect(result).not.toContain('Read and apply `instructions/prd-creator.md`'); + expect(result).not.toContain('Load Planner Skill'); + expect(result).toContain('The PRD will be used directly as a GitHub issue body'); }); - it("should include instructions to write the file", () => { + it('should include exact required PRD sections inline', () => { + const vars = createTestVars(); + + const result = renderSlicerPrompt(vars); + + expect(result).toContain('## 1. Context'); + expect(result).toContain('**Problem:**'); + expect(result).toContain('**Files Analyzed:**'); + expect(result).toContain('**Current Behavior:**'); + expect(result).toContain('## 2. Solution'); + expect(result).toContain('## 4. Execution Phases'); + expect(result).toContain('## 5. Acceptance Criteria'); + expect(result).toContain('The PRD MUST include these sections'); + }); + + it('should include complexity scoring guide', () => { + const vars = createTestVars(); + + const result = renderSlicerPrompt(vars); + + expect(result).toContain('Complexity Scoring'); + expect(result).toContain('Touches 1-5 files'); + expect(result).toContain('LOW'); + expect(result).toContain('MEDIUM'); + expect(result).toContain('HIGH'); + }); + + it('should include instructions to write the file', () => { const vars = createTestVars({ - outputFilePath: "/custom/path/feature.md", + outputFilePath: '/custom/path/feature.md', }); const result = renderSlicerPrompt(vars); - expect(result).toContain("/custom/path/feature.md"); - expect(result).toContain("Write tool"); + expect(result).toContain('/custom/path/feature.md'); + expect(result).toContain('Write tool'); }); - it("should use custom template when provided", () => { - const vars = createTestVars({ title: "Custom Title" }); - const customTemplate = "Custom Prompt: {{TITLE}} in {{SECTION}}"; + it('should use custom template when provided', () => { + const vars = createTestVars({ title: 'Custom Title' }); + const customTemplate = 'Custom Prompt: {{TITLE}} in {{SECTION}}'; const result = renderSlicerPrompt(vars, customTemplate); - expect(result).toBe("Custom Prompt: Custom Title in Features"); + expect(result).toBe('Custom Prompt: Custom Title in Features'); }); - it("should handle empty description", () => { - const vars = createTestVars({ description: "" }); + it('should handle empty description', () => { + const vars = createTestVars({ description: '' }); const result = renderSlicerPrompt(vars); // Should still render without errors - expect(result).toContain("Test Feature"); - expect(result).not.toContain("{{DESCRIPTION}}"); + expect(result).toContain('Test Feature'); + expect(result).not.toContain('{{DESCRIPTION}}'); }); - it("should handle special characters in values", () => { + it('should handle special characters in values', () => { const vars = createTestVars({ - title: "Feature with `backticks` and $pecial chars", - description: "Description with\nnewlines\nand **markdown**", + title: 'Feature with `backticks` and $pecial chars', + description: 'Description with\nnewlines\nand **markdown**', }); const result = renderSlicerPrompt(vars); - expect(result).toContain("Feature with `backticks` and $pecial chars"); - expect(result).toContain("Description with\nnewlines\nand **markdown**"); + expect(result).toContain('Feature with `backticks` and $pecial chars'); + expect(result).toContain('Description with\nnewlines\nand **markdown**'); }); }); - describe("loadSlicerTemplate", () => { - it("should load template from file", () => { + describe('loadSlicerTemplate', () => { + it('should load template from file', () => { const template = loadSlicerTemplate(); - expect(template).toContain("Principal Software Architect"); - expect(template).toContain("{{TITLE}}"); - expect(template).toContain("{{SECTION}}"); - expect(template).toContain("{{DESCRIPTION}}"); - expect(template).toContain("{{OUTPUT_FILE_PATH}}"); - expect(template).toContain("{{PRD_DIR}}"); + expect(template).toContain('Principal Software Architect'); + expect(template).toContain('{{TITLE}}'); + expect(template).toContain('{{SECTION}}'); + expect(template).toContain('{{DESCRIPTION}}'); + expect(template).toContain('{{OUTPUT_FILE_PATH}}'); + expect(template).toContain('{{PRD_DIR}}'); }); - it("should cache the template", () => { + it('should cache the template', () => { const template1 = loadSlicerTemplate(); const template2 = loadSlicerTemplate(); @@ -144,7 +169,7 @@ describe("slicer-prompt", () => { expect(template1).toBe(template2); }); - it("should clear cache when requested", () => { + it('should clear cache when requested', () => { loadSlicerTemplate(); clearTemplateCache(); @@ -155,38 +180,32 @@ describe("slicer-prompt", () => { }); }); - describe("createSlicerPromptVars", () => { - it("should create variables with correct paths", () => { + describe('createSlicerPromptVars', () => { + it('should create variables with correct paths', () => { const vars = createSlicerPromptVars( - "Feature Title", - "Section Name", - "Feature description", - "/path/to/prds", - "99-feature-title.md", + 'Feature Title', + 'Section Name', + 'Feature description', + '/path/to/prds', + '99-feature-title.md', ); - expect(vars.title).toBe("Feature Title"); - expect(vars.section).toBe("Section Name"); - expect(vars.description).toBe("Feature description"); - expect(vars.prdDir).toBe("/path/to/prds"); - expect(vars.outputFilePath).toBe("/path/to/prds/99-feature-title.md"); + expect(vars.title).toBe('Feature Title'); + expect(vars.section).toBe('Section Name'); + expect(vars.description).toBe('Feature description'); + expect(vars.prdDir).toBe('/path/to/prds'); + expect(vars.outputFilePath).toBe('/path/to/prds/99-feature-title.md'); }); - it("should handle empty description with default", () => { - const vars = createSlicerPromptVars( - "Feature", - "Section", - "", - "/prds", - "file.md", - ); + it('should handle empty description with default', () => { + const vars = createSlicerPromptVars('Feature', 'Section', '', '/prds', 'file.md'); - expect(vars.description).toBe("(No description provided)"); + expect(vars.description).toBe('(No description provided)'); }); }); - describe("integration with prd-creator format", () => { - it("should produce a prompt that instructs AI to explore the codebase", () => { + describe('integration with prd-creator format', () => { + it('should produce a prompt that instructs AI to explore the codebase', () => { const vars = createTestVars(); const result = renderSlicerPrompt(vars); @@ -194,14 +213,14 @@ describe("slicer-prompt", () => { expect(result).toMatch(/codebase/i); }); - it("should produce a prompt that instructs AI to assess complexity", () => { + it('should produce a prompt that instructs AI to assess complexity', () => { const vars = createTestVars(); const result = renderSlicerPrompt(vars); expect(result).toMatch(/assess.*complexity/i); }); - it("should produce a prompt that instructs AI to write a complete PRD with all required sections", () => { + it('should produce a prompt that instructs AI to write a complete PRD with all required sections', () => { const vars = createTestVars(); const result = renderSlicerPrompt(vars); @@ -212,9 +231,9 @@ describe("slicer-prompt", () => { expect(result).toMatch(/acceptance.?criteria/i); }); - it("should emphasize the exact output file path", () => { + it('should emphasize the exact output file path', () => { const vars = createTestVars({ - outputFilePath: "/exact/path/to/prd.md", + outputFilePath: '/exact/path/to/prd.md', }); const result = renderSlicerPrompt(vars); diff --git a/packages/core/src/templates/slicer-prompt.ts b/packages/core/src/templates/slicer-prompt.ts index 6a180060..7c924451 100644 --- a/packages/core/src/templates/slicer-prompt.ts +++ b/packages/core/src/templates/slicer-prompt.ts @@ -57,9 +57,19 @@ The PRD directory is: \`{{PRD_DIR}}\` ## Your Task -1. **Explore the Codebase** — Read relevant existing files to understand structure, patterns, and conventions. +1. **Explore the Codebase** — Read relevant existing files to understand structure, patterns, and conventions. Look for: + - \`CLAUDE.md\` or similar AI assistant documentation + - Existing code in the area you'll be modifying + - Related features or modules this feature interacts with + - Test patterns and verification commands + - \`.env\` files or env-loading utilities (use those patterns, never hardcode values) + 2. **Assess Complexity** — Score using the rubric below and determine LOW / MEDIUM / HIGH. -3. **Write a Complete PRD** — Follow the exact template structure below. Every section must be filled with concrete information. + +3. **Write a Complete PRD** — Follow the exact template structure below. Every section must be filled with concrete information. No placeholder text. + + The PRD MUST include these sections: Context (with Problem, Files Analyzed, Current Behavior, Integration Points), Solution (with Approach, Key Decisions), Execution Phases (with Files, Implementation steps, Tests), and Acceptance Criteria. + 4. **Write the PRD File** — Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`. --- @@ -67,6 +77,7 @@ The PRD directory is: \`{{PRD_DIR}}\` ## Complexity Scoring \`\`\` +COMPLEXITY SCORE (sum all that apply): +1 Touches 1-5 files +2 Touches 6-10 files +3 Touches 10+ files @@ -87,79 +98,168 @@ The PRD directory is: \`{{PRD_DIR}}\` ## PRD Template Structure -Your PRD MUST use this structure. Replace every [bracketed placeholder] with real content. +Your PRD MUST follow this exact structure. Replace every \`[bracketed placeholder]\` with real content. -# PRD: [Title] +\`\`\`\`markdown +# PRD: [Title from roadmap item] **Complexity: [SCORE] → [LEVEL] mode** +- [Complexity breakdown as bullet list] + +--- + ## 1. Context -**Problem:** [1-2 sentences] +**Problem:** [1-2 sentences describing the issue being solved] **Files Analyzed:** -- \`path/to/file.ts\` — [what you found] + +- \`path/to/file.ts\` — [what the file does / what you looked for] +- [List every file you read before writing this PRD] **Current Behavior:** -- [3-5 bullets] + +- [3-5 bullets describing what happens today] ### Integration Points -- Entry point: [cron / CLI / event / route] -- Caller file: [file invoking new code] -- User flow: User does X → triggers Y → result Z + +**How will this feature be reached?** + +- [ ] Entry point: [cron job / CLI command / event / API route] +- [ ] Caller file: [file that will invoke this new code] +- [ ] Registration/wiring: [anything that must be connected] + +**Is this user-facing?** + +- [ ] YES → UI components required: [list them] +- [ ] NO → Internal/background (explain how it is triggered) + +**Full user flow:** + +1. User does: [action] +2. Triggers: [code path] +3. Reaches new feature via: [specific connection point] +4. Result: [what the user sees] + +--- ## 2. Solution **Approach:** -- [3-5 bullets] -**Key Decisions:** [library choices, error handling, reused utilities] +- [3-5 bullets explaining the chosen solution] -**Data Changes:** [schema changes, or "None"] +**Architecture Diagram** (MEDIUM/HIGH): -## 3. Sequence Flow (MEDIUM/HIGH only) +\`\`\`mermaid +flowchart LR + A[Component A] --> B[Component B] --> C[Result] +\`\`\` + +**Key Decisions:** + +- [Library/framework choices, error-handling strategy, reused utilities] -[mermaid sequenceDiagram] +**Data Changes:** [New schemas/migrations, or "None"] + +--- + +## 3. Sequence Flow (MEDIUM/HIGH) + +\`\`\`mermaid +sequenceDiagram + participant A as ComponentA + participant B as ComponentB + A->>B: methodName(args) + alt Error + B-->>A: ErrorType + else Success + B-->>A: Response + end +\`\`\` + +--- ## 4. Execution Phases -### Phase N: [Name] — [User-visible outcome] +Critical rules: +1. Each phase = ONE user-testable vertical slice +2. Max 5 files per phase (split if larger) +3. Each phase MUST include concrete tests +4. Checkpoint after each phase + +### Phase 1: [Name] — [User-visible outcome in 1 sentence] **Files (max 5):** + - \`src/path/file.ts\` — [what changes] **Implementation:** + - [ ] Step 1 +- [ ] Step 2 **Tests Required:** | Test File | Test Name | Assertion | |-----------|-----------|-----------| -| \`src/__tests__/feature.test.ts\` | \`should X when Y\` | \`expect(r).toBe(Z)\` | +| \`src/__tests__/feature.test.ts\` | \`should do X when Y\` | \`expect(result).toBe(Z)\` | + +**Verification Plan:** + +1. **Unit Tests:** [file and test names] +2. **User Verification:** + - Action: [what to do] + - Expected: [what should happen] **Checkpoint:** Run \`yarn verify\` and related tests after this phase. +--- + +[Repeat Phase block for each additional phase] + +--- + ## 5. Acceptance Criteria - [ ] All phases complete -- [ ] All tests pass +- [ ] All specified tests pass - [ ] \`yarn verify\` passes -- [ ] Feature is reachable (not orphaned code) +- [ ] Feature is reachable (entry point connected, not orphaned code) +- [ ] [Feature-specific criterion] +- [ ] [Feature-specific criterion] +\`\`\`\` --- ## Critical Instructions -1. Read all relevant files BEFORE writing the PRD -2. Follow existing patterns — use \`@/*\` path aliases, match naming conventions -3. Include concrete file paths and implementation steps -4. Include specific test names and assertions -5. Use the Write tool to create the file at \`{{OUTPUT_FILE_PATH}}\` -6. No placeholder text in the final PRD -7. The PRD is the GitHub issue body — make it self-contained +1. **Read all relevant files BEFORE writing the PRD** — never guess at file paths or API shapes +2. **Follow existing patterns** — reuse utilities, match naming conventions, use path aliases (\`@/*\`) +3. **Concrete file paths and implementation details** — no vague steps +4. **Specific test names and assertions** — not "write tests" +5. **Use the Write tool** to create the file at \`{{OUTPUT_FILE_PATH}}\` +6. **No placeholder text** in the final PRD — every section must have real content +7. **Self-contained** — the PRD will be read as a GitHub issue; it must make sense without context -DO NOT leave [bracketed placeholder] text in the output. +DO NOT leave \`[bracketed placeholder]\` text in the output. DO NOT skip any sections. DO NOT forget to write the file. + +--- + +## Output + +After writing the PRD file, report: + +\`\`\` +PRD Creation Complete +File: {{OUTPUT_FILE_PATH}} +Title: [PRD title] +Complexity: [score] → [level] +Phases: [count] +Summary: [1-2 sentences] +\`\`\` `; // Cache for the loaded template diff --git a/templates/slicer.md b/templates/slicer.md index 82f1e6f4..76258470 100644 --- a/templates/slicer.md +++ b/templates/slicer.md @@ -32,6 +32,8 @@ The PRD directory is: `{{PRD_DIR}}` 3. **Write a Complete PRD** — Follow the exact template structure below. Every section must be filled with concrete information. No placeholder text. + The PRD MUST include these sections: Context (with Problem, Files Analyzed, Current Behavior, Integration Points), Solution (with Approach, Key Decisions), Execution Phases (with Files, Implementation steps, Tests), and Acceptance Criteria. + 4. **Write the PRD File** — Use the Write tool to create the PRD file at `{{OUTPUT_FILE_PATH}}`. --- @@ -146,6 +148,7 @@ sequenceDiagram ## 4. Execution Phases Critical rules: + 1. Each phase = ONE user-testable vertical slice 2. Max 5 files per phase (split if larger) 3. Each phase MUST include concrete tests