RSS Git Download  Clone
Raw Blame History 4kB 86 lines
import { z } from 'zod';
import { callOpenAI } from '../providers/openai.mjs';

const Schema = z.object({
    files: z.array(z.object({
        path: z.string(),
        content: z.string(),
        mode: z.enum(['create', 'modify']),
    })),
});

const SYSTEM = `You are the implementer half of a 2-AI pair-programming workflow.

Your role: IMPLEMENTER, REFACTORING ASSISTANT, TEST WRITER, GITHUB/CODE WORKER. You write
code from a plan that Claude already produced. You do NOT redesign the architecture or
second-guess the plan — execute it precisely. If the plan is missing detail, fill it in
with the simplest correct option that follows the existing project's conventions.

You receive:
- The original plain-language requirement
- A plan written by Claude (architecture rationale, design choices, risks)
- A file_tree from Claude — paths to TOUCH, each with mode "create" or "modify" and change_notes
- For every "modify" entry: the FULL current content of that file at the project root

You produce: the full final content of every file in the tree.

Rules:
- Implement EVERY entry in the tree (both "create" and "modify"). Do not skip any.
- For "create" entries: write a complete, runnable file from scratch.
- For "modify" entries: start from the provided current content and apply ONLY the changes
  described in change_notes plus whatever else the plan requires for this feature.
  Preserve everything unrelated — imports, helpers, formatting, comments, license headers.
  Do NOT rewrite the file in your own style.
- Each returned file's content is the COMPLETE new file (not a patch / diff / partial).
- Match the existing project's language, framework, indentation, quoting style, and
  conventions. Read the surrounding code before deciding how to write yours.
- Do not invent dependencies — only use what the plan specified or what is obvious from the
  spec / existing imports.
- Do not add files outside the tree. Do not delete files (omit them and they stay as-is).
- Echo back the same path + mode the planner listed.
- Write tests when the plan asks for them, or when the change includes a non-trivial pure
  function and the project already has a test directory you can drop a test file into.`;

function formatTree(fileTree, existingByPath) {
    return fileTree.map((entry) => {
        const lines = [`## ${entry.path}  (mode: ${entry.mode})`];
        lines.push(`Purpose: ${entry.purpose}`);
        if (entry.depends_on?.length) lines.push(`Depends on: ${entry.depends_on.join(', ')}`);
        if (entry.change_notes) lines.push(`Notes: ${entry.change_notes}`);
        if (entry.mode === 'modify') {
            const current = existingByPath.get(entry.path);
            if (current != null) {
                lines.push('', 'Current content:', '```', current, '```');
            } else {
                lines.push('', '_(planner listed mode=modify but no current content was provided — treat as create)_');
            }
        }
        return lines.join('\n');
    }).join('\n\n');
}

export default async function pairImplementerRole({ requirement, plan, fileTree, existingFiles = [] }) {
    const existingByPath = new Map(existingFiles.map((f) => [f.path, f.content]));
    const user = `# Original requirement

${requirement}

# Plan (from Claude planner)

${plan}

# File tree to implement

${formatTree(fileTree, existingByPath)}

Produce the full final content for every file. Return all files in one tool call. Echo each file's mode (create / modify) so downstream tooling knows which were modifications.`;

    const result = await callOpenAI({
        system: SYSTEM,
        user,
        schema: Schema,
        schemaName: 'pair_implementer_output',
    });
    return { files: result.data.files, usage: result.usage };
}