1use serde::Deserialize;
4
5use crate::agent::{self, Agent};
6use crate::config::{Call, Config};
7use crate::error::Result;
8use crate::repo::Repo;
9use crate::{bail, log, schema};
10
11const PROMPT: &str = "\
12Draft one detailed GitHub issue for the project in your working directory,
13using the request below. Read the README, relevant source, tests, and project
14instructions first so the issue fits this repository and its conventions.
15
16Preserve the user's intent and constraints. Describe the problem or motivation,
17current behavior, requested behavior, scope, concrete acceptance criteria, and
18a test plan. Include relevant file paths and implementation guidance when the
19code supports them. For a bug, include supplied or verified reproduction steps
20and expected versus actual behavior. Do not invent reproductions, observed
21results, existing APIs, or requirements. Label assumptions and open questions
22explicitly. Keep the issue focused on the request, without unrelated work.
23Use Markdown headings and checkboxes for acceptance criteria.
24
25This is issue drafting only. Do not implement the request, modify files,
26commit, push, or create issues, comments, or pull requests. Spar will file the
27returned title and body and run its normal triage and implementation workflow.
28If you need scratch space, use the system temporary directory.
29
30User request:
31";
32
33#[derive(Debug, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct Draft {
36 pub title: String,
37 pub body: String,
38}
39
40pub fn validate_prompt(prompt: &str) -> Result<()> {
41 if prompt.trim().is_empty() {
42 bail!("spar code needs a nonempty project prompt");
43 }
44 Ok(())
45}
46
47pub fn draft(agents: &[Agent], cfg: &Config, repo: &Repo, request: &str) -> Result<Draft> {
50 validate_prompt(request)?;
51 let agent = agent::find(agents, &cfg.first_implementor)?;
52 log!("drafting an issue from the project prompt");
53 let answer: Draft = agent.ask_json(
54 &format!("{PROMPT}{}", request.trim()),
55 &schema::code_issue(),
56 repo.root(),
57 &agent.effort(cfg, Call::Code),
58 )?;
59 let title = repo.clean_title(&answer.title)?;
60 let body = repo.clean_issue_body(&answer.body)?;
61 if title.trim().is_empty() || body.trim().is_empty() {
62 bail!("issue draft must have a nonempty title and body after style cleanup");
63 }
64 Ok(Draft { title, body })
65}