planner / src/prompt.rs

raw file 5057 bytes
use crate::model::ProjectPlan;

pub fn render_prompt(plan: &ProjectPlan) -> String {
    let mut s = String::new();
    s.push_str("# Project Implementation Plan\n\n");
    s.push_str("You are an expert software engineer. Implement the following project carefully, with minimal errors, following the plan, stack, and standards below. Ask clarifying questions only if truly necessary; otherwise proceed.\n\n");

    s.push_str("## 1. Project Overview\n");
    s.push_str(&format!("- **Name:** {}\n", plan.name));
    s.push_str(&format!("- **Description:** {}\n", plan.description));
    s.push_str(&format!("- **Type:** {}\n", plan.project_type));
    s.push_str(&format!("- **Audience:** {}\n", plan.audience));
    s.push_str(&format!("- **Scale:** {}\n", plan.scale));
    s.push_str(&format!("- **Created:** {}\n\n", plan.created_at));

    s.push_str("## 2. Requirements\n");
    for r in &plan.requirements {
        if r.key != "constraint" {
            s.push_str(&format!("- **{}:** {}\n", r.key, r.value));
        }
    }
    s.push('\n');

    let constraints: Vec<&crate::model::Requirement> = plan.requirements.iter().filter(|r| r.key == "constraint").collect();
    if !constraints.is_empty() {
        s.push_str("## Constraints\n");
        for r in constraints {
            s.push_str(&format!("- {}\n", r.value));
        }
        s.push('\n');
    }

    s.push_str("## 3. Chosen Stack\n");
    s.push_str(&format!("- **Language:** {}\n", plan.stack.language));
    if let Some(f) = &plan.stack.framework {
        s.push_str(&format!("- **Framework:** {}\n", f));
    }
    s.push_str(&format!("- **Libraries:** {}\n", plan.stack.libraries.join(", ")));
    s.push_str(&format!("- **Rationale:** {}\n\n", plan.stack.rationale));

    s.push_str("## 4. Libraries (details)\n");
    for l in &plan.libraries {
        s.push_str(&format!("- **{}** ({}, {}): {}{}\n", l.name, l.lang, l.domain, l.purpose,
            l.note.as_ref().map(|n| format!(" — {}", n)).unwrap_or_default()));
    }
    s.push('\n');

    s.push_str("## 5. Architecture\n");
    s.push_str(&format!("{}\n\n", plan.architecture));

    s.push_str("## 6. Suggested File Structure\n");
    for f in &plan.file_tree {
        s.push_str(&format!("- `{}`\n", f));
    }
    s.push('\n');

    s.push_str("## 7. Feature Plan\n");
    let feature_reqs: Vec<&crate::model::Requirement> = plan.requirements.iter().filter(|r| r.key == "feature").collect();
    if feature_reqs.is_empty() {
        s.push_str("- (no specific features listed; derive from description)\n");
    } else {
        for r in feature_reqs {
            s.push_str(&format!("- {}\n", r.value));
        }
    }
    s.push('\n');

    s.push_str("## 8. Implementation Plan (Milestones)\n");
    for m in &plan.milestones {
        s.push_str(&format!("### {} — {}\n", m.name, m.goal));
        for t in &m.tasks {
            s.push_str(&format!("- [ ] **{}** {}: {}\n", t.id, t.title, t.description));
            if !t.depends_on.is_empty() {
                s.push_str(&format!("      (depends on: {})\n", t.depends_on.join(", ")));
            }
        }
        s.push('\n');
    }

    s.push_str("## 9. Coding Standards\n");
    for c in &plan.coding_standards {
        s.push_str(&format!("- {}\n", c));
    }
    s.push('\n');

    s.push_str("## 10. Risks & Mitigations\n");
    if plan.risks.is_empty() {
        s.push_str("- None identified.\n");
    } else {
        for r in &plan.risks {
            s.push_str(&format!("- {}\n", r));
        }
    }
    s.push('\n');

    s.push_str("## Common Pitfalls\n");
    if plan.pitfalls.is_empty() {
        s.push_str("- None identified.\n");
    } else {
        for p in &plan.pitfalls {
            s.push_str(&format!("- {}\n", p));
        }
    }
    s.push('\n');

    if let Some(t) = &plan.template {
        s.push_str(&format!("## 11. Template Used\n- {}\n\n", t));
    }
    if let Some(g) = &plan.language_guidance {
        s.push_str("## Language Guidance\n");
        s.push_str(g);
        s.push_str("\n\n");
    }
    if !plan.domain.is_empty() {
        s.push_str(&format!("## Domain\n- {}\n\n", plan.domain));
    }
    if let Some(g) = &plan.domain_guidance {
        s.push_str("## Domain Guidance\n");
        s.push_str(g);
        s.push_str("\n\n");
    }
    if let Some(ai) = &plan.ai_assistance {
        s.push_str("## AI Assistant (Local Model) Notes\n");
        s.push_str(ai);
        s.push_str("\n\n");
    }

    s.push_str("## Final Instructions to the LLM\n");
    s.push_str("1. Follow the milestones in order; do not skip setup.\n");
    s.push_str("2. Use ONLY the chosen stack and libraries unless a strong reason exists; explain deviations.\n");
    s.push_str("3. Produce complete, runnable code for each file in the suggested structure.\n");
    s.push_str("4. Include tests for core logic.\n");
    s.push_str("5. Handle errors and edge cases; no panics in production paths.\n");
    s.push_str("6. End with a short summary of what was implemented and how to run it.\n");

    s
}