atomcode_core/atomgit/
fixissue.rs1use anyhow::{anyhow, Result};
5
6use crate::auth;
7
8use super::client::Client;
9use super::models::{Comment, Issue};
10use super::url::{detect_cwd_atomgit_repo, IssueRef, RepoRef};
11
12pub const FIXED_LABEL: &str = "fixed";
14
15pub enum Prepared {
18 Run {
19 prompt: String,
20 issue_title: String,
21 issue_number: u64,
22 issue_ref: IssueRef,
25 },
26 Skip {
27 reason: String,
28 },
29}
30
31pub fn post_completion(issue_ref: &IssueRef, summary: &str) -> anyhow::Result<()> {
40 let client = Client::from_stored_auth()?;
41 client.post_issue_comment(issue_ref, summary)?;
42 client.add_issue_label(issue_ref, FIXED_LABEL)?;
43 Ok(())
44}
45
46pub fn prepare(issue_url: &str, working_dir: &std::path::Path) -> Result<Prepared> {
50 let r = IssueRef::parse(issue_url)?;
51
52 let issue_repo = RepoRef::from(&r);
61 let mut cwd_hint: Option<RepoRef> = None;
62 match detect_cwd_atomgit_repo(working_dir) {
63 Ok(Some(cwd_repo)) => {
64 if !cwd_repo.matches(&issue_repo) {
65 return Ok(Prepared::Skip {
66 reason: format!(
67 "cwd points to {}/{} but the issue is in {}/{}. Skipping — cd to the matching repo first (or pass the right URL).",
68 cwd_repo.owner, cwd_repo.repo, issue_repo.owner, issue_repo.repo
69 ),
70 });
71 }
72 cwd_hint = Some(cwd_repo);
73 }
74 Ok(None) => {
75 }
77 Err(_) => {
78 }
80 }
81
82 let me = auth::current_user()
83 .ok_or_else(|| anyhow!("not logged in — run `atomcode login` first"))?;
84
85 let client = Client::from_stored_auth()?;
86 let issue = client.get_issue(&r)?;
87 let _ = cwd_hint;
90
91 if !issue.is_assigned_to(&me.username) {
92 return Ok(Prepared::Skip {
93 reason: format!(
94 "issue #{} is assigned to {}, not you ({}). Skipping — fixissue only runs on issues assigned to the current user.",
95 issue.number,
96 issue.assignee_list(),
97 me.username
98 ),
99 });
100 }
101
102 let comments = client.get_issue_comments(&r);
103 let prompt = build_prompt(&issue, &comments, working_dir, &r);
104 Ok(Prepared::Run {
105 prompt,
106 issue_title: issue.title.clone(),
107 issue_number: issue.number,
108 issue_ref: r,
109 })
110}
111
112fn build_prompt(
113 issue: &Issue,
114 comments: &[Comment],
115 working_dir: &std::path::Path,
116 r: &IssueRef,
117) -> String {
118 let mut out = String::new();
119 out.push_str(&format!(
120 "请分析并修复以下 AtomGit issue,直接在当前本地项目里改代码(不要 commit 或 push,改完即可)。\n\n"
121 ));
122 out.push_str("---\n");
123 out.push_str(&format!("## Issue #{}: {}\n", issue.number, issue.title));
124 if let Some(url) = &issue.html_url {
125 out.push_str(&format!("URL: {}\n", url));
126 } else {
127 out.push_str(&format!(
128 "URL: https://atomgit.com/{}/{}/issues/{}\n",
129 r.owner, r.repo, r.number
130 ));
131 }
132 out.push_str(&format!("状态: {}\n", issue.state));
133 if !issue.labels.is_empty() {
134 let labels: Vec<&str> = issue.labels.iter().map(|l| l.name.as_str()).collect();
135 out.push_str(&format!("标签: {}\n", labels.join(", ")));
136 }
137 if let Some(reporter) = &issue.user {
138 out.push_str(&format!("报告人: {}\n", reporter.login));
139 }
140 out.push_str(&format!("工作目录: {}\n", working_dir.display()));
141 out.push_str("\n### 正文\n\n");
142 out.push_str(issue.body.as_deref().unwrap_or("(空)"));
143 out.push('\n');
144
145 let with_body: Vec<&Comment> = comments
146 .iter()
147 .filter(|c| {
148 c.body
149 .as_deref()
150 .map(|b| !b.trim().is_empty())
151 .unwrap_or(false)
152 })
153 .collect();
154 if !with_body.is_empty() {
155 out.push_str(&format!("\n### 评论 ({})\n", with_body.len()));
156 for (i, c) in with_body.iter().enumerate() {
157 let author = c
158 .user
159 .as_ref()
160 .map(|u| u.login.as_str())
161 .unwrap_or("unknown");
162 let body = c.body.as_deref().unwrap_or("");
163 out.push_str(&format!("\n**#{} — @{}:**\n{}\n", i + 1, author, body));
164 }
165 }
166
167 out.push_str("\n---\n\n");
168 out.push_str(
169 "请按以下步骤处理:\n\
170 1. 先总结 issue 的核心问题(一段话);\n\
171 2. 定位代码中相关文件、给出修复方案;\n\
172 3. 直接在本地项目里实施修复(可调用工具编辑文件、跑测试);\n\
173 4. 修复完成后用 `## 修复摘要` 为标题,写一段简洁摘要,包含:\n\
174 - 改动的文件清单\n\
175 - 核心修复逻辑说明\n\
176 - 是否通过编译 / 测试\n\
177 \n⚠️ 不要执行 git commit / push,只改本地文件。\n\
178 \n📝 重要:你在这轮对话里的所有文本回复会被自动发布到上述 issue 的评论区作为修复记录,\
179 修复完成后,issue 会被自动打上 `fixed` 标签。请确保输出的内容适合作为正式评论。\n",
180 );
181 out
182}