Skip to content

Raw comments #298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions crates/rust-project-goals-cli-llm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,18 @@ async fn main() -> anyhow::Result<()> {
} = Opt::parse();
let UpdateArgs {
milestone,
quick,
vscode,
output_file,
start_date,
end_date,
model_id,
region,
} = &serde_json::from_str(&updates_json)?;
updates::updates(
&repository,
milestone,
output_file.as_deref(),
start_date,
end_date,
*quick,
*vscode,
model_id.as_deref(),
region.as_deref(),
)
.await?;
Ok(())
Expand Down
9 changes: 6 additions & 3 deletions crates/rust-project-goals-cli-llm/src/templates.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};

use handlebars::{DirectorySourceOptions, Handlebars};
use rust_project_goals::gh::issues::ExistingGithubComment;
use serde::Serialize;

use rust_project_goals_json::Progress;
Expand Down Expand Up @@ -44,8 +45,7 @@ handlebars::handlebars_helper!(is_complete: |p: Progress| match p {
pub struct Updates {
pub milestone: String,
pub flagship_goals: Vec<UpdatesGoal>,
pub other_goals_with_updates: Vec<UpdatesGoal>,
pub other_goals_without_updates: Vec<UpdatesGoal>,
pub other_goals: Vec<UpdatesGoal>,
}

impl Updates {
Expand Down Expand Up @@ -74,7 +74,10 @@ pub struct UpdatesGoal {
pub is_closed: bool,

/// Markdown with update text (bullet list)
pub updates_markdown: String,
pub comments: Vec<ExistingGithubComment>,

/// Comments.len but accessible to the template
pub num_comments: usize,

/// Progress towards the goal
pub progress: Progress,
Expand Down
164 changes: 13 additions & 151 deletions crates/rust-project-goals-cli-llm/src/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,25 @@ use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};

use crate::llm::LargeLanguageModel;
use crate::templates::{self, Updates, UpdatesGoal};
use rust_project_goals::gh::issues::ExistingGithubIssue;
use rust_project_goals::gh::{
issue_id::{IssueId, Repository},
issues::{checkboxes, list_issues_in_milestone, ExistingGithubComment},
};

const QUICK_UPDATES: &[&str] = &[
"Jack and Jill went up the hill",
"To fetch a pail of water",
"Jack fell down and broke his crown",
"And Jill came tumbling after.",
"Up Jack got and home did trot,",
"As fast as he could caper;",
"Went to bed to mend his head",
"With vinegar and brown paper.",
"Jill came in and she did grin",
"To see his paper plaster;",
"Mother, vex’d, did whip her next",
"For causing Jack's disaster.",
];

fn comments_forever() -> impl Iterator<Item = &'static str> {
QUICK_UPDATES.iter().copied().cycle()
}

pub async fn updates(
repository: &Repository,
milestone: &str,
output_file: Option<&Path>,
start_date: &Option<NaiveDate>,
end_date: &Option<NaiveDate>,
quick: bool,
vscode: bool,
model_id: Option<&str>,
region: Option<&str>,
) -> anyhow::Result<()> {
if output_file.is_none() && !vscode {
anyhow::bail!("either `--output-file` or `--vscode` must be specified");
}

let llm = LargeLanguageModel::new(model_id, region).await?;

let issues = list_issues_in_milestone(repository, milestone)?;

let filter = Filter {
Expand All @@ -69,14 +44,10 @@ pub async fn updates(

let mut updates = templates::Updates {
milestone: milestone.to_string(),
flagship_goals: vec![],
other_goals_with_updates: vec![],
other_goals_without_updates: vec![],
flagship_goals: prepare_goals(repository, &issues, &filter, true).await?,
other_goals: prepare_goals(repository, &issues, &filter, false).await?,
};

prepare_flagship_goals(repository, &issues, &filter, &llm, quick, &mut updates).await?;
prepare_other_goals(repository, &issues, &filter, &llm, quick, &mut updates).await?;

progress_bar::finalize_progress_bar();

// Render the output using handlebars and write it to the file.
Expand Down Expand Up @@ -108,17 +79,16 @@ pub async fn updates(
Ok(())
}

async fn prepare_flagship_goals(
async fn prepare_goals(
repository: &Repository,
issues: &[ExistingGithubIssue],
filter: &Filter<'_>,
llm: &LargeLanguageModel,
quick: bool,
updates: &mut Updates,
) -> anyhow::Result<()> {
// First process the flagship goals, for which we capture the full text of comments.
flagship: bool,
) -> anyhow::Result<Vec<UpdatesGoal>> {
let mut result = vec![];
// We process flagship and regular goals in two passes, and capture comments differently for flagship goals.
for issue in issues {
if !issue.has_flagship_label() {
if flagship != issue.has_flagship_label() {
continue;
}

Expand All @@ -135,34 +105,9 @@ async fn prepare_flagship_goals(

let mut comments = issue.comments.clone();
comments.sort_by_key(|c| c.created_at.clone());
comments.retain(|c| filter.matches(c));
comments.retain(|c| !c.is_automated_comment() && filter.matches(c));

let summary: String = if comments.len() == 0 {
format!("No updates in this period.")
} else if quick {
QUICK_UPDATES.iter().copied().collect()
} else {
let prompt = format!(
"The following comments are updates to a project goal entitled '{title}'. \
The goal is assigned to {people} ({assignees}). \
Summarize the major developments, writing for general Rust users. \
Write the update in the third person and do not use pronouns when referring to people. \
Do not respond with anything but the summary paragraphs. \
",
people = if issue.assignees.len() == 1 {
"1 person".to_string()
} else {
format!("{} people", issue.assignees.len())
},
assignees = comma(&issue.assignees),
);
let updates: String = comments.iter().map(|c| format!("\n{}\n", c.body)).collect();
llm.query(&prompt, &updates)
.await
.with_context(|| format!("making request to LLM failed"))?
};

updates.flagship_goals.push(UpdatesGoal {
result.push(UpdatesGoal {
title: title.clone(),
issue_number: issue.number,
issue_assignees: comma(&issue.assignees),
Expand All @@ -173,96 +118,13 @@ async fn prepare_flagship_goals(
.url(),
progress,
is_closed: issue.state == GithubIssueState::Closed,
updates_markdown: summary,
num_comments: comments.len(),
comments,
});

progress_bar::inc_progress_bar();
}
Ok(())
}

async fn prepare_other_goals(
repository: &Repository,
issues: &[ExistingGithubIssue],
filter: &Filter<'_>,
llm: &LargeLanguageModel,
quick: bool,
updates: &mut Updates,
) -> anyhow::Result<()> {
// Next process the remaining goals, for which we generate a summary using an LLVM.
let mut quick_comments = comments_forever();
for issue in issues {
if issue.has_flagship_label() {
continue;
}

let title = &issue.title;

progress_bar::print_progress_bar_info(
&format!("Issue #{number}", number = issue.number),
title,
progress_bar::Color::Green,
progress_bar::Style::Bold,
);

// Find the relevant updates that have occurred.
let mut comments = issue.comments.clone();
comments.sort_by_key(|c| c.created_at.clone());
comments.retain(|c| filter.matches(c));

// Use an LLM to summarize the updates.
let summary = if comments.len() == 0 {
format!("* No updates in this period.")
} else if quick {
let num_comments = std::cmp::min(comments.len(), 3);
quick_comments
.by_ref()
.take(num_comments)
.map(|c| format!("* {c}\n"))
.collect()
} else {
let prompt = format!(
"The following comments are updates to a project goal entitled '{title}'. \
The goal is assigned to {people} ({assignees}). \
Summarize the updates with a list of one or two bullet points, each one sentence. \
Write the update in the third person and do not use pronouns when referring to people. \
Format the bullet points as markdown with each bullet point beginning with `* `. \
Do not respond with anything but the bullet points. \
",
people = if issue.assignees.len() == 1 {
"1 person".to_string()
} else {
format!("{} people", issue.assignees.len())
},
assignees = comma(&issue.assignees),
);
let updates: String = comments.iter().map(|c| format!("\n{}\n", c.body)).collect();
llm.query(&prompt, &updates).await?
};

let goal = UpdatesGoal {
title: title.clone(),
issue_number: issue.number,
issue_assignees: comma(&issue.assignees),
issue_url: IssueId {
repository: repository.clone(),
number: issue.number,
}
.url(),
is_closed: issue.state == GithubIssueState::Closed,
updates_markdown: summary,
progress: checkboxes(&issue),
};

if comments.len() > 0 {
updates.other_goals_with_updates.push(goal);
} else {
updates.other_goals_without_updates.push(goal);
}

progress_bar::inc_progress_bar();
}
Ok(())
Ok(result)
}

struct Filter<'f> {
Expand Down
23 changes: 19 additions & 4 deletions crates/rust-project-goals-cli/src/rfc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use rust_project_goals::{
gh::{
issue_id::{IssueId, Repository},
issues::{
change_milestone, create_comment, create_issue, fetch_issue, list_issues_in_milestone, lock_issue, sync_assignees, update_issue_body, FLAGSHIP_LABEL, LOCK_TEXT
change_milestone, change_title, create_comment, create_issue, fetch_issue, list_issues_in_milestone, lock_issue, sync_assignees, update_issue_body, CONTINUING_GOAL_PREFIX, FLAGSHIP_LABEL, LOCK_TEXT
},
labels::GhLabel,
},
Expand Down Expand Up @@ -199,6 +199,11 @@ enum GithubAction<'doc> {
issue: GithubIssue<'doc>,
},

ChangeTitle {
number: u64,
title: String,
},

ChangeMilestone {
number: u64,
milestone: String,
Expand Down Expand Up @@ -333,6 +338,10 @@ fn initialize_issues<'doc>(
.collect(),
});
}

if existing_issue.title != desired_issue.title {
actions.insert(GithubAction::ChangeTitle { number: existing_issue.number, title: desired_issue.title });
}

if existing_issue.milestone.as_ref().map(|m| m.title.as_str()) != Some(timeframe) {
actions.insert(GithubAction::ChangeMilestone {
Expand All @@ -342,9 +351,7 @@ fn initialize_issues<'doc>(
actions.insert(GithubAction::Comment {
number: existing_issue.number,
body: format!(
"This is a continuing project goal, and the updates below \
this comment will be for the new period {}",
timeframe
"{CONTINUING_GOAL_PREFIX} {timeframe}",
),
});
}
Expand Down Expand Up @@ -525,6 +532,9 @@ impl Display for GithubAction<'_> {
GithubAction::ChangeMilestone { number, milestone } => {
write!(f, "update issue #{} milestone to \"{}\"", number, milestone)
}
GithubAction::ChangeTitle { number, title } => {
write!(f, "update issue #{} title to \"{}\"", number, title)
}
GithubAction::Comment { number, body } => {
write!(f, "post comment on issue #{}: \"{}\"", number, body)
}
Expand Down Expand Up @@ -596,6 +606,11 @@ impl GithubAction<'_> {
Ok(())
}

GithubAction::ChangeTitle { number, title } => {
change_title(repository, number, &title)?;
Ok(())
}

GithubAction::Comment { number, body } => {
create_comment(repository, number, &body)?;
Ok(())
Expand Down
12 changes: 0 additions & 12 deletions crates/rust-project-goals-llm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ pub struct UpdateArgs {
/// Milestone for which we generate tracking issue data (e.g., `2024h2`).
pub milestone: String,

/// Quick mode does not use an LLM to generate a summary.
#[arg(long)]
pub quick: bool,

/// Quick mode does not use an LLM to generate a summary.
#[arg(long)]
pub vscode: bool,
Expand All @@ -30,12 +26,4 @@ pub struct UpdateArgs {
/// End date for comments.
/// If not given, no end date.
pub end_date: Option<chrono::NaiveDate>,

/// Set a custom model id for the LLM.
#[arg(long)]
pub model_id: Option<String>,

/// Set a custom region.
#[arg(long)]
pub region: Option<String>,
}
Loading