diff --git a/.gitignore b/.gitignore index af906242..acb0e1b4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ src/api # copied into the repo by `mdbook-mermaid` install: /mermaid*.js + +.agents +.kiro diff --git a/crates/mdbook-goals/src/goal_preprocessor.rs b/crates/mdbook-goals/src/goal_preprocessor.rs index ce71cd15..05acc794 100644 --- a/crates/mdbook-goals/src/goal_preprocessor.rs +++ b/crates/mdbook-goals/src/goal_preprocessor.rs @@ -206,6 +206,15 @@ impl<'c> GoalPreprocessorWithContext<'c> { // Handle filtered lists of goals with needs self.replace_goals_with_needs_lists_filtered(chapter)?; + // Handle funding summary table + self.replace_funding_table(chapter)?; + + // Handle roadmap-filtered funding table + self.replace_funding_table_filtered(chapter)?; + + // Handle grouped funding table + self.replace_funding_table_grouped(chapter)?; + Ok(()) } @@ -336,12 +345,147 @@ impl<'c> GoalPreprocessorWithContext<'c> { &mut self, chapter: &mut Chapter, ) -> anyhow::Result<()> { - self.replace_themed_goal_list( - chapter, - &re::GOALS_WITH_NEEDS_LIST_FILTERED, - "(((GOALS WITH NEEDS: ...)))", - |g| &g.metadata.needs, - ) + loop { + let Some(m) = re::GOALS_WITH_NEEDS_LIST_FILTERED.find(&chapter.content) else { + return Ok(()); + }; + let range = m.range(); + + let chapter_path = chapter_path(chapter, "(((GOALS WITH NEEDS: ...)))")?; + + let capture_value = re::GOALS_WITH_NEEDS_LIST_FILTERED + .captures(&chapter.content[range.clone()]) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str().trim()) + .unwrap(); + + let goals = self.goal_documents(chapter_path)?; + + let mut filtered_goals: Vec<&GoalDocument> = if capture_value == "Funding" { + goals + .iter() + .filter(|g| { + g.metadata.status.content.is_not_not_accepted() && g.needs_funding() + }) + .collect() + } else if capture_value == "Contributor" { + goals + .iter() + .filter(|g| { + g.metadata.status.content.is_not_not_accepted() + && (g.needs_contributor() || g.metadata.is_help_wanted()) + }) + .collect() + } else { + goals + .iter() + .filter(|g| { + g.metadata.status.content.is_not_not_accepted() + && g.metadata.needs.contains(capture_value) + }) + .collect() + }; + + filtered_goals.sort_by_key(|g| &g.metadata.title); + + let heading_re = Regex::new(r"(?m)^(#+)\s").unwrap(); + let preceding = &chapter.content[..range.start]; + let context_level = heading_re + .find_iter(preceding) + .last() + .map(|m| m.as_str().trim().len()) + .unwrap_or(1); + + let output = if capture_value == "Funding" { + goal::format_funding_goal_sections(&filtered_goals, context_level + 1) + .into_anyhow()? + } else if capture_value == "Contributor" { + goal::format_help_wanted_goal_sections(&filtered_goals, context_level + 1) + .into_anyhow()? + } else { + goal::format_highlight_goal_sections(&filtered_goals, context_level + 1) + .into_anyhow()? + }; + + chapter.content.replace_range(range, &output); + } + } + + fn replace_funding_table(&mut self, chapter: &mut Chapter) -> anyhow::Result<()> { + let Some(m) = re::FUNDING_TABLE.find(&chapter.content) else { + return Ok(()); + }; + let range = m.range(); + + let chapter_path = chapter_path(chapter, "(((FUNDING TABLE)))")?; + let goals = self.goal_documents(chapter_path)?; + + let mut filtered_goals: Vec<&GoalDocument> = goals + .iter() + .filter(|g| g.metadata.status.content.is_not_not_accepted() && g.needs_funding()) + .collect(); + + filtered_goals.sort_by_key(|g| &g.metadata.title); + + let output = goal::format_funding_table(&filtered_goals); + chapter.content.replace_range(range, &output); + Ok(()) + } + + fn replace_funding_table_filtered(&mut self, chapter: &mut Chapter) -> anyhow::Result<()> { + let Some(m) = re::FUNDING_TABLE_FILTERED.find(&chapter.content) else { + return Ok(()); + }; + let range = m.range(); + + let chapter_path = chapter_path(chapter, "(((FUNDING TABLE: ...)))")?; + + let filter_value = re::FUNDING_TABLE_FILTERED + .captures(&chapter.content[range.clone()]) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str().trim()) + .unwrap(); + let filter_value = filter_value.to_string(); + + let goals = self.goal_documents(chapter_path)?; + + let mut filtered_goals: Vec<&GoalDocument> = goals + .iter() + .filter(|g| { + g.metadata.status.content.is_not_not_accepted() + && g.needs_funding() + && g.matches_roadmap_theme(&filter_value) + }) + .collect(); + + filtered_goals.sort_by_key(|g| &g.metadata.title); + + let output = goal::format_funding_table(&filtered_goals); + chapter.content.replace_range(range, &output); + Ok(()) + } + + fn replace_funding_table_grouped(&mut self, chapter: &mut Chapter) -> anyhow::Result<()> { + let Some(m) = re::FUNDING_TABLE_GROUPED.find(&chapter.content) else { + return Ok(()); + }; + let range = m.range(); + + let chapter_path = chapter_path(chapter, "(((FUNDING TABLE GROUPED)))")?; + let goals = self.goal_documents(chapter_path)?; + let roadmaps = self.roadmap_documents(chapter_path)?; + + let mut filtered_goals: Vec<&GoalDocument> = goals + .iter() + .filter(|g| g.metadata.status.content.is_not_not_accepted() && g.needs_funding()) + .collect(); + + filtered_goals.sort_by_key(|g| &g.metadata.title); + + let roadmap_refs: Vec<&goal::RoadmapDocument> = roadmaps.iter().collect(); + let output = goal::format_funding_table_grouped(&filtered_goals, &roadmap_refs); + chapter.content.replace_range(range, &output); + Ok(()) } /// Shared helper for replacing themed goal list directives (HIGHLIGHT GOALS, GOALS WITH NEEDS). diff --git a/crates/rust-project-goals-cli/src/review.rs b/crates/rust-project-goals-cli/src/review.rs index c9db0629..d82b50f6 100644 --- a/crates/rust-project-goals-cli/src/review.rs +++ b/crates/rust-project-goals-cli/src/review.rs @@ -176,7 +176,7 @@ fn format_review( for goal in sorted_goals { // Help wanted marker - let help_wanted = if goal.metadata.is_help_wanted() { + let help_wanted = if goal.needs_contributor() || goal.metadata.is_help_wanted() { " ![Help Wanted][]" } else { "" diff --git a/crates/rust-project-goals/src/goal.rs b/crates/rust-project-goals/src/goal.rs index d9e26376..28537393 100644 --- a/crates/rust-project-goals/src/goal.rs +++ b/crates/rust-project-goals/src/goal.rs @@ -14,6 +14,99 @@ use crate::team::{self, TeamName}; use crate::util::{self, commas, markdown_files}; use rust_project_goals_json::{GithubIssueState, Progress}; +/// A parsed funding cost value. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum FundingCost { + /// A known dollar amount in USD (stored as whole dollars). + Usd(u64), + /// The cost is not yet determined. + Tbd, +} + +impl FundingCost { + /// Parse a cost string like "$25,000", "$75K", "$1.5M", or "TBD". + pub fn parse(s: &str) -> Option { + let s = s.trim(); + if s.eq_ignore_ascii_case("TBD") { + return Some(FundingCost::Tbd); + } + let s = s.strip_prefix('$')?; + let s = s.replace(',', ""); + let s = s.trim(); + if let Some(base) = s.strip_suffix('K').or_else(|| s.strip_suffix('k')) { + let n: f64 = base.trim().parse().ok()?; + Some(FundingCost::Usd((n * 1_000.0) as u64)) + } else if let Some(base) = s.strip_suffix('M').or_else(|| s.strip_suffix('m')) { + let n: f64 = base.trim().parse().ok()?; + Some(FundingCost::Usd((n * 1_000_000.0) as u64)) + } else { + let n: u64 = s.parse().ok()?; + Some(FundingCost::Usd(n)) + } + } + + /// Format as a display string (e.g., "$75,000" or "TBD"). + pub fn display(&self) -> String { + match self { + FundingCost::Tbd => "TBD".to_string(), + FundingCost::Usd(amount) => format!("${}", format_with_commas(*amount)), + } + } +} + +fn format_with_commas(n: u64) -> String { + let s = n.to_string(); + let mut result = String::new(); + for (i, c) in s.chars().rev().enumerate() { + if i > 0 && i % 3 == 0 { + result.push(','); + } + result.push(c); + } + result.chars().rev().collect() +} + +/// Sum a slice of FundingCost values. Returns None if all are TBD, otherwise sums the known amounts. +pub fn sum_funding_costs(costs: &[FundingCost]) -> Option { + let mut total: u64 = 0; + let mut any_known = false; + let mut any_tbd = false; + for cost in costs { + match cost { + FundingCost::Usd(n) => { + total += n; + any_known = true; + } + FundingCost::Tbd => { + any_tbd = true; + } + } + } + if !any_known && any_tbd { + Some(FundingCost::Tbd) + } else if any_known { + Some(FundingCost::Usd(total)) + } else { + None + } +} + +/// A single row from the `## Funding` section's `| Purpose | Cost | Status |` table. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct FundingItem { + pub purpose: String, + pub cost: FundingCost, + pub status: String, +} + +/// A single row from the `## Help wanted` section's `| Task | Experience level | Time investment |` table. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct HelpWantedItem { + pub task: String, + pub experience_level: String, + pub time_investment: String, +} + /// Data parsed from a goal file in the expected format #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct GoalDocument { @@ -46,6 +139,14 @@ pub struct GoalDocument { /// Hierarchical task structure parsed from "Work items over the next year". /// When it has children, roadmap tables show one row per child instead of one row for the goal. pub task_tree: TaskTree, + + /// Funding items parsed from the `## Funding` section (if present). + /// Presence of this section means the goal needs funding. + pub funding: Vec, + + /// Help wanted items parsed from the `## Help wanted` section (if present). + /// Presence of this section means the goal needs a contributor. + pub help_wanted: Vec, } /// Data parsed from a roadmap file (`roadmap-*.md`). @@ -593,6 +694,12 @@ impl GoalDocument { // Compute task_owners from the task tree task_owners.extend(task_tree.all_task_owners()); + // Extract funding items from the `## Funding` section (if present) + let funding = extract_funding(§ions)?; + + // Extract help wanted items from the `## Help wanted` section (if present) + let help_wanted = extract_help_wanted(§ions)?; + Ok(Some(GoalDocument { path: path.to_path_buf(), link_path, @@ -602,6 +709,8 @@ impl GoalDocument { goal_plans, task_owners, task_tree, + funding, + help_wanted, })) } @@ -610,6 +719,16 @@ impl GoalDocument { self.team_involvement.teams() } + /// True if this goal needs funding (has a `## Funding` section). + pub fn needs_funding(&self) -> bool { + !self.funding.is_empty() + } + + /// True if this goal needs a contributor (has a `## Help wanted` section). + pub fn needs_contributor(&self) -> bool { + !self.help_wanted.is_empty() + } + /// True if this goal is a candidate (may yet be accepted) pub fn is_not_not_accepted(&self) -> bool { self.metadata.status.is_not_not_accepted() @@ -629,7 +748,7 @@ impl GoalDocument { /// In goal lists, we render our point-of-contact as "Help Wanted" if this goal needs a contributor. pub fn point_of_contact_for_goal_list(&self) -> String { - if self.metadata.is_help_wanted() { + if self.needs_contributor() || self.metadata.is_help_wanted() { "![Help Wanted][]".to_string() } else { self.metadata.pocs.clone() @@ -814,7 +933,7 @@ pub fn format_highlight_goal_sections( let hashes = "#".repeat(heading_level); for goal in goals { - if goal.metadata.is_help_wanted() { + if goal.needs_contributor() || goal.metadata.is_help_wanted() { output.push_str(&format!( "{hashes} [{}]({}) ![Help wanted][]\n\n", *goal.metadata.title, @@ -853,6 +972,223 @@ pub fn format_highlight_goal_sections( Ok(output) } +/// Format a summary table of goals needing funding. +/// Columns: Goal (linked to page#funding), Cost, Status, What and why. +pub fn format_funding_table(goals: &[&GoalDocument]) -> String { + let mut output = String::new(); + output.push_str("| Goal | Cost | Status | What and why |\n"); + output.push_str("| --- | --- | --- | --- |\n"); + for goal in goals { + format_funding_table_row(&mut output, goal); + } + output +} + +/// Format a summary table of goals needing funding, grouped by roadmap. +/// Each roadmap gets a bold header row linking to the roadmap page. +/// Goals not in any roadmap go under "Other goals". +pub fn format_funding_table_grouped( + goals: &[&GoalDocument], + roadmaps: &[&RoadmapDocument], +) -> String { + let mut output = String::new(); + output.push_str("| Goal | Cost | Status | What and why |\n"); + output.push_str("| --- | --- | --- | --- |\n"); + + let mut used: Vec = vec![false; goals.len()]; + let mut all_costs: Vec = vec![]; + + // Sort roadmaps alphabetically by short title + let mut sorted_roadmaps: Vec<&&RoadmapDocument> = roadmaps.iter().collect(); + sorted_roadmaps.sort_by_key(|r| &r.short_title); + + for roadmap in sorted_roadmaps { + let theme = roadmap.short_title.content.trim(); + let matching: Vec = goals + .iter() + .enumerate() + .filter(|(_, g)| g.matches_roadmap_theme(theme)) + .map(|(i, _)| i) + .collect(); + + if matching.is_empty() { + continue; + } + + // Collect costs for subtotal + let group_costs: Vec = matching + .iter() + .flat_map(|&i| goals[i].funding.iter().map(|f| f.cost.clone())) + .collect(); + let subtotal = sum_funding_costs(&group_costs); + let subtotal_str = subtotal.map(|c| c.display()).unwrap_or_default(); + + // Emit roadmap header row with subtotal + output.push_str(&format!( + "| **[{}]({})** | **{}** | | |\n", + theme, + roadmap.link_path.display(), + subtotal_str, + )); + + for i in &matching { + used[*i] = true; + all_costs.extend(goals[*i].funding.iter().map(|f| f.cost.clone())); + format_funding_table_row(&mut output, goals[*i]); + } + } + + // Emit "Other goals" for anything not in a roadmap + let other: Vec = (0..goals.len()).filter(|i| !used[*i]).collect(); + if !other.is_empty() { + let group_costs: Vec = other + .iter() + .flat_map(|&i| goals[i].funding.iter().map(|f| f.cost.clone())) + .collect(); + let subtotal = sum_funding_costs(&group_costs); + let subtotal_str = subtotal.map(|c| c.display()).unwrap_or_default(); + + output.push_str(&format!("| **Other goals** | **{}** | | |\n", subtotal_str)); + for i in &other { + all_costs.extend(goals[*i].funding.iter().map(|f| f.cost.clone())); + format_funding_table_row(&mut output, goals[*i]); + } + } + + // Grand total + if let Some(total) = sum_funding_costs(&all_costs) { + output.push_str(&format!("| **Total** | **{}** | | |\n", total.display())); + } + + output +} + +fn format_funding_table_row(output: &mut String, goal: &GoalDocument) { + let total = sum_funding_costs( + &goal + .funding + .iter() + .map(|f| f.cost.clone()) + .collect::>(), + ); + let cost_str = total.map(|c| c.display()).unwrap_or_default(); + let status = goal + .funding + .first() + .map(|f| f.status.as_str()) + .unwrap_or(""); + output.push_str(&format!( + "| [{}]({}#funding) | {} | {} | {} |\n", + *goal.metadata.title, + goal.link_path.display(), + cost_str, + status, + goal.what_and_why(), + )); +} + +/// Format goals that need funding as sections with people, summary, and funding table. +pub fn format_funding_goal_sections( + goals: &[&GoalDocument], + heading_level: usize, +) -> Result { + let mut output = String::new(); + let hashes = "#".repeat(heading_level); + + for goal in goals { + output.push_str(&format!( + "{hashes} [{}]({})\n\n", + *goal.metadata.title, + goal.link_path.display() + )); + + // Build people list + let mut people: Vec = Vec::new(); + people.push(format!("{} (point of contact)", goal.metadata.pocs)); + for owner in &goal.task_owners { + if !goal.metadata.pocs.contains(owner.as_str()) { + people.push(owner.clone()); + } + } + for (team, champion) in &goal.metadata.champions { + people.push(format!("{} ({} champion)", champion.content, team)); + } + if !people.is_empty() { + output.push_str(&format!("*{}*\n\n", people.join(", "))); + } + + output.push_str(goal.summary.trim()); + output.push_str("\n\n"); + + // Render funding table + if !goal.funding.is_empty() { + output.push_str("| Purpose | Cost | Status |\n"); + output.push_str("|---------|------|--------|\n"); + for item in &goal.funding { + output.push_str(&format!( + "| {} | {} | {} |\n", + item.purpose, + item.cost.display(), + item.status + )); + } + output.push('\n'); + } + } + + Ok(output) +} + +/// Format goals that need a contributor as sections with people, summary, and help wanted table. +pub fn format_help_wanted_goal_sections( + goals: &[&GoalDocument], + heading_level: usize, +) -> Result { + let mut output = String::new(); + let hashes = "#".repeat(heading_level); + + for goal in goals { + output.push_str(&format!( + "{hashes} [{}]({})\n\n", + *goal.metadata.title, + goal.link_path.display() + )); + + // Build people list + let mut people: Vec = Vec::new(); + people.push(format!("{} (point of contact)", goal.metadata.pocs)); + for owner in &goal.task_owners { + if !goal.metadata.pocs.contains(owner.as_str()) { + people.push(owner.clone()); + } + } + for (team, champion) in &goal.metadata.champions { + people.push(format!("{} ({} champion)", champion.content, team)); + } + if !people.is_empty() { + output.push_str(&format!("*{}*\n\n", people.join(", "))); + } + + output.push_str(goal.summary.trim()); + output.push_str("\n\n"); + + // Render help wanted table + if !goal.help_wanted.is_empty() { + output.push_str("| Task | Experience level | Time investment |\n"); + output.push_str("|------|-----------------|------------------|\n"); + for item in &goal.help_wanted { + output.push_str(&format!( + "| {} | {} | {} |\n", + item.task, item.experience_level, item.time_investment + )); + } + output.push('\n'); + } + } + + Ok(output) +} + /// Format highlight goals as a table with "Goal" and "What and why" columns. /// When a goal has subgoals (task tree children), emits one row per child /// instead of one row for the goal itself. @@ -1244,6 +1580,53 @@ pub fn extract_summary(sections: &[Section]) -> Result> { Ok(Some(ownership_section.text.trim().to_string())) } +/// Extract funding items from the `## Funding` section, if present. +fn extract_funding(sections: &[Section]) -> Result> { + let Some(section) = sections.iter().find(|s| s.title == "Funding") else { + return Ok(vec![]); + }; + + let Some(table) = section.tables.first() else { + return Ok(vec![]); + }; + + expect_headers(table, &["Purpose", "Cost", "Status"])?; + + let mut items = vec![]; + for row in &table.rows { + let cost = FundingCost::parse(row[1].trim()).unwrap_or(FundingCost::Tbd); + items.push(FundingItem { + purpose: row[0].to_string(), + cost, + status: row[2].to_string(), + }); + } + Ok(items) +} + +/// Extract help wanted items from the `## Help wanted` section, if present. +fn extract_help_wanted(sections: &[Section]) -> Result> { + let Some(section) = sections.iter().find(|s| s.title == "Help wanted") else { + return Ok(vec![]); + }; + + let Some(table) = section.tables.first() else { + return Ok(vec![]); + }; + + expect_headers(table, &["Task", "Experience level", "Time investment"])?; + + let mut items = vec![]; + for row in &table.rows { + items.push(HelpWantedItem { + task: row[0].to_string(), + experience_level: row[1].to_string(), + time_investment: row[2].to_string(), + }); + } + Ok(items) +} + struct RoadmapMetadata { title: Spanned, short_title: Spanned, diff --git a/crates/rust-project-goals/src/re.rs b/crates/rust-project-goals/src/re.rs index ea775ed8..7ac36b42 100644 --- a/crates/rust-project-goals/src/re.rs +++ b/crates/rust-project-goals/src/re.rs @@ -107,6 +107,23 @@ lazy_static! { pub static ref ROADMAP_CHAPTERS: Regex = Regex::new(r"\(\(\(ROADMAP CHAPTERS\)\)\)").unwrap(); } +// Summary table of goals needing funding with their total cost and what-and-why +lazy_static! { + pub static ref FUNDING_TABLE: Regex = Regex::new(r"\(\(\(FUNDING TABLE\)\)\)").unwrap(); +} + +// Summary table of goals needing funding, filtered by roadmap theme +lazy_static! { + pub static ref FUNDING_TABLE_FILTERED: Regex = + Regex::new(r"\(\(\(FUNDING TABLE:\s*(.+?)\s*\)\)\)").unwrap(); +} + +// Summary table of goals needing funding, grouped by roadmap with header rows +lazy_static! { + pub static ref FUNDING_TABLE_GROUPED: Regex = + Regex::new(r"\(\(\(FUNDING TABLE GROUPED\)\)\)").unwrap(); +} + // Roadmap goal rows (no headers) filtered by roadmap name. // Used inside manually authored markdown tables: `| (((ROADMAP ROWS: Theme))) |` lazy_static! { diff --git a/src/2026/afidt-box.md b/src/2026/afidt-box.md index 3f2f19f5..ae7eee25 100644 --- a/src/2026/afidt-box.md +++ b/src/2026/afidt-box.md @@ -6,7 +6,6 @@ | Status | Proposed | | What and why | Enable dyn dispatch for async traits via `.box` notation | | Timespan | 2026-2027 | -| Needs | Contributor | | Roadmap | Just add async | | Tracking issue | | | Highlight | Async and ergonomic RC | @@ -96,6 +95,12 @@ For more details and a broader look, see the [box, box, box][box-post] blog post | [compiler] | Medium | Implementation review | | [types] | Small | May have changes to dyn-compatibility rules | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions ### Why `.box` instead of implicit boxing? diff --git a/src/2026/async-statemachine-optimisation.md b/src/2026/async-statemachine-optimisation.md index 6a0fd1b2..79fc05bd 100644 --- a/src/2026/async-statemachine-optimisation.md +++ b/src/2026/async-statemachine-optimisation.md @@ -4,10 +4,8 @@ |:--------------------|-------------| | Point of contact | @diondokter | | Status | Proposed | -| Needs | Funding | | Tracking issue | | | Zulip channel | N/A | -| Needs | Funding | | [compiler] champion | @eholk | ## Summary @@ -88,6 +86,12 @@ I've got 3 optimisations on my list so far. You can see them in the work items a | ---------- | ------------- | --------------------------------------------------------------------------------------------------------------- | | [compiler] | Medium | Most will be review work, but pushing optimisations to the max will possibly touch on some controversial points that need discussion | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | TBD | 💬 Under discussion | + ## Frequently asked questions ### Why this, why now? diff --git a/src/2026/cargo-plumbing.md b/src/2026/cargo-plumbing.md index d963e921..749c807e 100644 --- a/src/2026/cargo-plumbing.md +++ b/src/2026/cargo-plumbing.md @@ -4,7 +4,6 @@ |:-----------------|----------------------------------------------------------------------------------| | Point of contact | @epage | | Status | Proposed | -| Needs | Contributor | | Tracking issue | [rust-lang/rust-project-goals#264] | | Zulip channel | N/A (an existing stream can be re-used or new streams can be created on request) | @@ -86,4 +85,10 @@ See [2025h2 goal](../2025h2/cargo-plumbing.md) for more background. | ---------- | ------------- | --------------------------------------- | | [cargo] | Small | PR reviews for Cargo changes; design discussions | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions diff --git a/src/2026/contributors.md b/src/2026/contributors.md new file mode 100644 index 00000000..a7019e2f --- /dev/null +++ b/src/2026/contributors.md @@ -0,0 +1,7 @@ +# Looking for contributors + +The goals below are looking for a contributor to do the implementation work. The goal summaries include details of the expected time commitment and work duration. If you might be interested in helping with a goal, please join the rust-lang Zulip and post a message in the [`#project-goals/2026-workshop`][channel] channel: tell us a bit about yourself, and mention us (the goals team) in that message, like `Hello @T-goals!`. You could also write to the goals team at `goals-team@rust-lang.org`! We would love to help you learn more about it. + +[channel]: https://rust-lang.zulipchat.com/#narrow/channel/546987-project-goals.2F2026-workshop + +(((GOALS WITH NEEDS: Contributor))) diff --git a/src/2026/ergonomic-rc.md b/src/2026/ergonomic-rc.md index 11194261..d95885a9 100644 --- a/src/2026/ergonomic-rc.md +++ b/src/2026/ergonomic-rc.md @@ -5,7 +5,6 @@ | Point of contact | @nikomatsakis | | Status | Proposed | | What and why | A `Share` trait for clone-as-alias types and `move($expr)` for precise closure capture control | -| Needs | Contributor | | Roadmap | [Just add async](./roadmap-just-add-async.md) | | Tracking issue | [rust-lang/rust-project-goals#107] | | Highlight | Async and ergonomic RC | @@ -125,6 +124,12 @@ Precise control over what closures capture and when, eliminating the need for aw | [lang-docs] | Small | | | [libs-api] | Small | Reviews of RFC and API surface area | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions ### This goal looks very different than I remembered. What has changed? diff --git a/src/2026/expansion-time-evaluation.md b/src/2026/expansion-time-evaluation.md index 5c2e77a5..bd71a021 100644 --- a/src/2026/expansion-time-evaluation.md +++ b/src/2026/expansion-time-evaluation.md @@ -9,14 +9,11 @@ | [compiler] champion | @petrochenkov | | Tracking issue | | | Zulip channel | N/A | -| Needs | Funding | ## Summary Lay the architectural groundwork for functions that can be evaluated during macro expansion. This speculative work focuses on "queryifying" the resolver and implementing a restricted trait solver mode, enabling future language features like `macro fn` and compiler-integrated interop tools. -**Needs funding:** This goal needs funding to proceed. - ## Motivation ### The status quo @@ -67,6 +64,12 @@ We will prototype decoupling macro expansion from the monolithic resolver. This | [compiler] | Large | Significant refactoring of the resolver, reviews from @petrochenkov | | [types] | Medium | Support for the restricted solver mode in the new solver | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | TBD | 🔍 Looking | + ## Frequently asked questions ### Does this implement `macro fn`? diff --git a/src/2026/funding.md b/src/2026/funding.md new file mode 100644 index 00000000..3c6f9861 --- /dev/null +++ b/src/2026/funding.md @@ -0,0 +1,7 @@ +# Looking for funding + +The goals below are looking for funding. If you might be interested in helping to fund a goal, please join the rust-lang Zulip and post a message in the [`#project-goals/2026-workshop`][channel] channel or write to the goals team at `goals-team@rust-lang.org`. + +[channel]: https://rust-lang.zulipchat.com/#narrow/channel/546987-project-goals.2F2026-workshop + +(((FUNDING TABLE GROUPED))) diff --git a/src/2026/help-wanted.md b/src/2026/help-wanted.md index 4c4c79f2..8a47abc8 100644 --- a/src/2026/help-wanted.md +++ b/src/2026/help-wanted.md @@ -1,18 +1,6 @@ # Help wanted ![Help Wanted][] -This section contains goals that are in need of help. - -## Looking for contributors - -The goals below are looking for a contributor to do the implementation work. The goal summaries include details of the expected time commitment and work duration. If you might be interested in helping with a goal, please join the rust-lang Zulip and post a message in the [`#project-goals/2026-workshop`][channel] channel: tell us a bit about yourself, and mention us (the goals team) in that message, like `Hello @T-goals!`. You could also write to the goals team at `goals-team@rust-lang.org`! We would love to help you learn more about it. - -[channel]: https://rust-lang.zulipchat.com/#narrow/channel/546987-project-goals.2F2026-workshop - -(((GOALS WITH NEEDS: Contributor))) - -## Looking for funds - -The goals below are looking for funding. The goal summaries include details of the expected time commitment and work duration. If you might be interested in helping to fund a goal, please join the rust-lang Zulip and post a message in the [`#project-goals/2026-workshop`][channel] channel or write to the goals team at `goals-team@rust-lang.org`. - -(((GOALS WITH NEEDS: Funding))) +This section contains goals that are in need of help. See the subpages for details: +- [Looking for contributors](./contributors.md) — goals that need someone to do the implementation work +- [Looking for funding](./funding.md) — goals that need financial support to proceed diff --git a/src/2026/improve-cg_clif-performance.md b/src/2026/improve-cg_clif-performance.md index 45784c93..4cd1bc4c 100644 --- a/src/2026/improve-cg_clif-performance.md +++ b/src/2026/improve-cg_clif-performance.md @@ -6,15 +6,13 @@ | Status | Proposed | | Tracking issue | | | Zulip channel | N/A | -| Needs | Funding | +| Roadmap | Fast Builds | | [compiler] champion | @bjorn3 | ## Summary This goal aims to improve the rust development experience through faster incremental code generation with `rustc_codegen_cranelift`. We additionally want to fix several long-standing bugs that currently prevent `rustc_codegen_cranelift` from being used for popular crates. -**Needs funding:** This goal needs funding to proceed. - ## Motivation ### The status quo @@ -63,4 +61,10 @@ We additionally want to fix several long-standing bugs that limit `rustc_codegen | [cargo] | Small | In case we end up pursuing JITing as a way to improve performance that will eventually need native integration with `cargo run`. For now we're just prototyping, and so the occasional vibe check should be sufficient | | [compiler] | Medium | Depending on what ways we end up pursuing, we might need no rustc side changes at all or medium sized changes. | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | $75,000 | ✅ Finalized | + ## Frequently asked questions diff --git a/src/2026/incremental-system-rethought.md b/src/2026/incremental-system-rethought.md index 4e92b1ee..824dc00a 100644 --- a/src/2026/incremental-system-rethought.md +++ b/src/2026/incremental-system-rethought.md @@ -5,6 +5,7 @@ | Point of contact | @blyxyas | | Status | Proposed | | Tracking issue | | +| Roadmap | Fast Builds | | [compiler] champion | @jackh726 | | Zulip channel | N/A | diff --git a/src/2026/libtest-json.md b/src/2026/libtest-json.md index d0fdd821..b7e7b23d 100644 --- a/src/2026/libtest-json.md +++ b/src/2026/libtest-json.md @@ -4,7 +4,6 @@ | :--------------- | -------------------------------------------------------------------------------- | | Point of contact | @epage | | Status | Proposed | -| Needs | Contributor | | Tracking issue | [rust-lang/rust-project-goals#255] | | Zulip channel | N/A (an existing stream can be re-used or new streams can be created on request) | @@ -60,4 +59,10 @@ Most of that involves shifting responsibilities from the test harness to the tes | [libs-api] | Small | | | [testing-devex] | Small | Design discussions and review | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions diff --git a/src/2026/manually-drop-attr.md b/src/2026/manually-drop-attr.md index 81d5b553..126bce77 100644 --- a/src/2026/manually-drop-attr.md +++ b/src/2026/manually-drop-attr.md @@ -4,7 +4,6 @@ |:--------------------|----------------| | Point of contact | @thunderseethe | | Status | Proposed | -| Needs | Funding | | Tracking issue | | | Zulip channel | N/A | | [lang] champion | @Nadrieril | @@ -130,6 +129,12 @@ impl Destruct for UringState { | [opsem] | Small | | | [types] | Small | | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | TBD | 💬 Under discussion | + ## Frequently asked questions ### Why not a `#[reverse_drop_order]` attribute instead? diff --git a/src/2026/open-namespaces.md b/src/2026/open-namespaces.md index 475a71ea..fd6c9d4a 100644 --- a/src/2026/open-namespaces.md +++ b/src/2026/open-namespaces.md @@ -4,7 +4,6 @@ |:-----------------|----------------------------------------------------------------------------------| | Point of contact | @epage | | Status | Proposed | -| Needs | Contributor | | Tracking issue | [rust-lang/rust-project-goals#256] | | Zulip channel | N/A (an existing stream can be re-used or new streams can be created on request) | @@ -40,4 +39,10 @@ This covers multiple teams and needs a lot of coordination to balance the needs | [cargo] | Small | | | [compiler] | Small | Design discussions, PR review | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions diff --git a/src/2026/parallel-front-end.md b/src/2026/parallel-front-end.md index 9332ccb6..18241815 100644 --- a/src/2026/parallel-front-end.md +++ b/src/2026/parallel-front-end.md @@ -7,6 +7,7 @@ | Tracking issue | [rust-lang/rust-project-goals#121] | | See also | [rust-lang/rust#113349] | | Zulip channel | [#t-compiler/wg-parallel-rustc][channel] | +| Roadmap | Fast Builds | | [wg-parallel-rustc] champion | @petrochenkov | ## Summary diff --git a/src/2026/pub-priv.md b/src/2026/pub-priv.md index 6acab50e..02dc7bd5 100644 --- a/src/2026/pub-priv.md +++ b/src/2026/pub-priv.md @@ -4,7 +4,6 @@ |:-----------------|----------------------------------------------------------------------------------| | Point of contact | @epage | | Status | Proposed | -| Needs | Contributor | | Tracking issue | [rust-lang/rust-project-goals#272] | | Zulip channel | N/A (an existing stream can be re-used or new streams can be created on request) | | [compiler] champion | @petrochenkov | @@ -48,4 +47,10 @@ Work with [compiler] to identify a minimal subset of functionality for what the | [cargo] | Small | | | [compiler] | Medium | Design discussions, PR review | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions diff --git a/src/2026/roadmap-fast-builds.md b/src/2026/roadmap-fast-builds.md new file mode 100644 index 00000000..49cc9204 --- /dev/null +++ b/src/2026/roadmap-fast-builds.md @@ -0,0 +1,83 @@ +# Fast Builds + +| Metadata | | +|:-----------------|------------------------------------------------------------------------------------------| +| Short title | Fast Builds | +| What and why | Make Rust compilation fast — both from-scratch builds and incremental rebuilds during development | +| Point of contact | @lqd | + +## Summary + +Minimize the time it takes to build Rust projects, whether from scratch (CI, fresh checkouts, branch switches) or incrementally during the edit-test-debug loop. + +## Motivation + +### The status quo + +Compilation time is consistently one of the top pain points cited by Rust developers. It manifests in two distinct but related scenarios: + +**Clean builds** — CI pipelines often take 30+ minutes for medium-sized projects, discouraging frequent commits and slowing iteration cycles. Developers switching branches or starting on a fresh checkout face similar delays that break their flow. + +**Incremental rebuilds** — As projects grow, the time to see errors in the IDE starts to lag, going from "near instantaneous" to having a noticeable delay. This becomes worse when debugging tests, since developers must also wait for the test build to complete and execute. + +The Rust compiler has made progress on compilation performance over the years, with many hotspots addressed and optimizations implemented. However, there are still architectural limitations that prevent the compiler from fully utilizing modern hardware. The frontend remains largely single-threaded despite CI machines having 16+ cores. For debug builds, we spend time in LLVM optimization passes that provide little benefit for unoptimized code. And the incremental system, while helpful, misses opportunities to skip unnecessary work. + +### Design axioms + +* **No one thing.** Particularly for batch compilation, we're past the point of easy wins. Improving build times means doing many small improvements that add up. +* **End-to-end, not just the compiler.** From the user's perspective, time spent linking or waiting for Cargo coordination is still time spent waiting. We should consider the big picture, not limit our focus to the compiler proper. +* **Scenario matters.** Different workflows have different bottlenecks. By focusing on particular scenarios — like incremental rebuilds during development — we can find specialized wins (like RDR) that dramatically improve that case. + +### What we are shooting for + +**Clean builds:** You switch branches or start a fresh checkout. The build completes quickly enough that you can grab coffee and come back to a ready development environment, not wait through a lunch break. + +**Incremental rebuilds:** You are debugging a test or doing a refactoring. You edit the file. As you are editing, IDE feedback is instant. You click to rerun the test. The result is immediate. + +### How we get there + +| Goal | Timespan | What and why | +| --- | --- | --- | +| (((ROADMAP ROWS: Fast Builds))) | +| Relink-Don't-Rebuild (RDR) | 2026 | Avoid rebuilding downstream crates when only function bodies change, cutting rebuild times by 5-10x for common changes | +| Incremental, efficient linking with Wild | 2026 and beyond | Integrate support for Wild, an innovative, Rust-based linker focused on performance and incremental link times | +| TPDE backend integration | 2027 | TPDE is a fast compiler backend framework that compiles 10-20x faster than LLVM -O0 with similar code quality | +| Compiler performance optimizations | 2026-2027 | Targeted improvements to hot paths in type checking, trait resolution, and code generation | +| Better build parallelization | *Future* | Improve Cargo's ability to coordinate parallel compilation across and within crates | +| Crate slicing | *Future* | Don't compile the entire crate, just the parts you need | + +**Alternative backends for debug builds.** The biggest opportunity for clean builds comes from using faster backends for unoptimized code. LLVM is optimized for heavyweight optimization but not ideal for debug builds where compilation speed matters more than runtime performance. Cranelift (cg_clif) is already functional and provides speedups for debug builds. TPDE is a newer backend framework that compiles 10-20x faster than LLVM -O0 with similar code quality, showing even more promise. + +**Parallel rustc frontend.** Currently, rustc processes each crate single-threaded even when compiling multiple crates in parallel. Enabling intra-crate parallelism lets the compiler utilize all available cores, especially important for large crates that become bottlenecks in the build graph. + +**Relink-Don't-Rebuild (RDR).** Most users are building a workspace with multiple crates. Changes in a root crate can force downstream rebuilds that are typically unnecessary. The goal of Relink-Don't-Rebuild is to detect when changes are purely internal and skip the recompilation step, instead just invoking the linker. + +**Incremental, efficient linking with Wild.** The Wild linker is an innovative new project that can help RDR be more effective by optimizing the linking step itself. + +**End-to-end Incremental Compilation.** The current incremental system covers HIR and later phases but misses earlier work like name resolution. Extending incremental compilation to cover more phases and improving dependency tracking precision reduces unnecessary recompilation. + +**Shared artifacts across compilation modes.** The `cargo check` command is a useful way to get compilation errors (it also powers rust-analyzer's error reporting), but all the work that it does has to be redone when you later run `cargo build` or `cargo test`. The result is that people whose workflows include checking for errors, running lints, and running tests often wind up doing the same work over and over for each workflow. + +**Compiler performance optimizations.** Profiling shows that compilation time is dominated by phases like trait resolution and type checking. Targeted optimizations to these bottlenecks can provide improvements across all build types without requiring architectural changes. + +**Better build parallelization.** Better coordination between Cargo and rustc can enable more aggressive parallelization and reduce coordination overhead, especially important for large workspaces with complex dependency graphs. + +**Crate slicing.** The Rust compiler currently builds the entirety of your dependencies before it starts on your crate, even when you yourself only need a small part of that code. [Experiments suggest](https://yijunyu.github.io/cargo-slicer/) that compiling just the subset of dependencies that are actually used can improve build times anywhere from 1.4x to 30x, but doing this optimization well will require rearchitecting the compiler. + +## Funding + +(((FUNDING TABLE: Fast Builds))) + +## Frequently asked questions + +### What about optimized/release builds? + +These improvements primarily target debug builds where alternative backends provide the biggest wins. Optimized builds spend most time in LLVM optimization passes, which are harder to accelerate. However, frontend improvements and better parallelization will still provide meaningful speedups for release builds. + +### Will this help rust-analyzer? + +Many of these improvements will directly benefit rust-analyzer, especially frontend parallelization and smarter incremental compilation. The overall reduction in compiler work will improve IDE responsiveness. + +### How much faster will builds get? + +Alternative backends can provide significant improvements for debug builds, with additional gains from parallelization on multi-core machines. The exact speedup depends on the codebase characteristics and available hardware, but the goal is to make builds fast enough that they don't interrupt development workflows. diff --git a/src/2026/rtn.md b/src/2026/rtn.md index ba1e64f8..f83a47d5 100644 --- a/src/2026/rtn.md +++ b/src/2026/rtn.md @@ -5,7 +5,6 @@ | Point of contact | @traviscross | | Status | Proposed | | What and why | Name opaque types and bound async return types so `async fn` in traits works with `Send` and `dyn` | -| Needs | Contributor | | Roadmap | Just add async | | Tracking issue | | | Zulip channel | [#wg-async][channel] | @@ -159,6 +158,12 @@ We're looking to mentor one or more contributors on this goal. The work spans la | [lang] | Medium | RFC review, design discussions | | [types] | Medium | Stabilization report review, TAIT interactions | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions ### Why not stabilize RTN now? diff --git a/src/2026/specialization.md b/src/2026/specialization.md index caf1731b..6439295e 100644 --- a/src/2026/specialization.md +++ b/src/2026/specialization.md @@ -4,7 +4,6 @@ |:----------------------|----------------------| | Point of contact | @tmandry | | Status | Proposed | -| Needs | Funding | | Tracking issue | | | Zulip channel | N/A | | Help wanted | N/A | @@ -54,4 +53,10 @@ Given that specialization is largely blocked on design work to address known sou | [libs] | Small | | | [opsem] | Small | | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | TBD | 💬 Under discussion | + ## Frequently asked questions diff --git a/src/2026/stabilize-cargo-sbom.md b/src/2026/stabilize-cargo-sbom.md index 8a609e55..6cd5437d 100644 --- a/src/2026/stabilize-cargo-sbom.md +++ b/src/2026/stabilize-cargo-sbom.md @@ -4,7 +4,6 @@ |:----------------------|-------------------------------------------------| | Point of contact | @Shnatsel | | Status | Proposed | -| Needs | Contributor | | Tracking issue | | | Other tracking issues | https://github.com/rust-lang/cargo/issues/16565 | | Zulip channel | N/A | @@ -62,6 +61,12 @@ We will need: | ---------- | ------------- | --------------------------------------- | | [cargo] | Medium | | +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implementation work | TBD | TBD | + ## Frequently asked questions TODO - will fill in based on the review comments diff --git a/src/2026/stabilize-try.md b/src/2026/stabilize-try.md index 3427a7bb..6043ca35 100644 --- a/src/2026/stabilize-try.md +++ b/src/2026/stabilize-try.md @@ -4,7 +4,6 @@ |:----------------------|----------------------| | Point of contact | @tmandry | | Status | Proposed | -| Needs | Funding | | Other tracking issues | rust-lang/rust#84277 | | Zulip channel | N/A | | Highlight | Try, never, extern types | @@ -110,4 +109,10 @@ Because there are open design questions that haven't yet been resolved, we shoul | [libs-api] | Medium | | | [types] | Small | | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | TBD | 💬 Under discussion | + ## Frequently asked questions diff --git a/src/2026/stabilizing-f16.md b/src/2026/stabilizing-f16.md index 28aba7d3..9ca71a83 100644 --- a/src/2026/stabilizing-f16.md +++ b/src/2026/stabilizing-f16.md @@ -7,7 +7,6 @@ | Tracking issue | | | Other tracking issues | https://github.com/rust-lang/rust/issues/116909 | | Zulip channel | N/A | -| Needs | Funding | ## Summary @@ -15,8 +14,6 @@ In recent years we've seen increasing hardware support for the `f16` and `f128` With LLVM 22, the remaining blockers in the backends have been cleared for `f16`, and therefore stabilizing this type in 2026 is realistic. -**Needs funding:** This goal needs funding to proceed. - ## Motivation ### The status quo @@ -46,6 +43,12 @@ With @tgross35 as the dedicated reviewer, the asks of the teams are limited. | [libs-api] | small | | | [lang] | small | occasionally being fast-tracked would be nice | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | $25,000 | ✅ Finalized | + ## Frequently asked questions ### What do I do with this space? diff --git a/src/2026/tail-call-loop-match.md b/src/2026/tail-call-loop-match.md index 3df102aa..afeb89bf 100644 --- a/src/2026/tail-call-loop-match.md +++ b/src/2026/tail-call-loop-match.md @@ -7,7 +7,6 @@ | Tracking issue | | | Other tracking issues | https://github.com/rust-lang/rust/issues/112788, https://github.com/rust-lang/rust/issues/132306 | | Zulip channel | | -| Needs | Funding | | [lang] champion | @scottmcm | ## Summary @@ -44,6 +43,12 @@ In light of these design issues, we'd also like to continue development of `loop | [compiler] | small | We expect to only need normal reviews. | | [lang] | Medium | Some architectures cannot support guaranteed tail calls. Our current list of limitations is:

- `wasm32`/`wasm64` need the `tail-call` target feature to be enabled
- `powerpc` (when `elf1` is used) cannot tail call functions in other objects

Hence, rust code using guaranteed tail calls is not as portable as standard rust code. We need T-lang feedback on how to resolve this.

The all-hands is well-timed to figure out a solution. | +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor | $25,000 | ✅ Finalized | + ## Frequently asked questions ### What do I do with this space? diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ca9fd772..e54a4714 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,6 +7,8 @@ - [Overview](./2026/README.md) - [Highlights](./2026/highlights.md) - [Help wanted](./2026/help-wanted.md) + - [Looking for contributors](./2026/contributors.md) + - [Looking for funding](./2026/funding.md) - [Roadmaps](./2026/roadmaps.md) - [Goals](./2026/goals.md) - [Frequently asked questions](./2026/faq.md) diff --git a/src/TEMPLATE.md b/src/TEMPLATE.md index 6ebf5230..dd6f1375 100644 --- a/src/TEMPLATE.md +++ b/src/TEMPLATE.md @@ -13,12 +13,9 @@ > > The **status** should be **Proposed** for new goals or **Accepted** once approved. > -> If your goal **needs** something to proceed, add one or more `Needs` rows: -> * `Needs | Contributor` — the goal needs someone to step up and do the work. -> The project goals team will try to help you find someone. -> * `Needs | Funding` — the goal needs funding to proceed. +> If your goal needs a **contributor**, add a `## Help wanted` section (see below). > -> If you add a 'need', please add a section in the summary giving more details. +> If your goal needs **funding**, add a `## Funding` section (see below). | Metadata | | | :--------------- | -------------------------------------------------------------------------------- | @@ -89,6 +86,30 @@ | [types] | | | | ... | ... | *Feel free to add rows for other teams* | +## Help wanted + +> **Instructions:** If your goal needs a contributor, fill in the table below +> describing the work, the experience level needed, and the expected time investment. +> The presence of this section signals that the goal is looking for someone to do the work. +> The project goals team will try to help you find someone. +> Delete this section if your goal does not need a contributor. + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| *e.g., Implement the feature* | *e.g., Intermediate Rust* | *e.g., 3 months part-time* | + +## Funding + +> **Instructions:** If your goal needs funding to proceed, fill in the table below +> describing what the funding would cover and the estimated cost. +> The presence of this section signals that the goal is looking for financial support. +> Delete this section if your goal does not need funding. + +| Purpose | Cost | Status | +|---------|------|--------| +| *e.g., Contributor (6 months, full-time)* | *e.g., $60,000* | 🔍 Looking | +| *e.g., Maintenance* | *e.g., $10,000* | 🔍 Looking | + ## Frequently asked questions ### What do I do with this space? diff --git a/src/about/goal-format.md b/src/about/goal-format.md index 59e20428..2e7321b2 100644 --- a/src/about/goal-format.md +++ b/src/about/goal-format.md @@ -28,13 +28,12 @@ The recognized fields are: | **Zulip channel** | No | A link to the relevant Zulip stream for discussion. | | **Roadmap** | No | The name of a roadmap theme this goal belongs to, e.g. `Rust for Linux`. Can appear multiple times if the goal spans several roadmaps. | | **Highlight** | No | A category name for the highlights page. Can appear multiple times. | -| **Needs** | No | Signals that the goal needs something to proceed. Use `Contributor` (someone to do the work) or `Funding` (financial support). Can appear multiple times. | | **Timespan** | No | Overrides the default goal period, e.g. `2026-2027` for multi-year goals. | | **\[team\] champion** | No | The champion for a specific team, e.g. `[lang] champion \| @someone`. Medium and Large team asks require a champion. | | **Teams** | *Auto-injected* | Filled in automatically from team asks. Do not add this row yourself. | | **Task owners** | *Auto-injected* | Filled in automatically from work item tables. Do not add this row yourself. | -**Multiple-value fields:** `Roadmap`, `Highlight`, and `Needs` support multiple values by repeating the row: +**Multiple-value fields:** `Roadmap` and `Highlight` support multiple values by repeating the row: ```markdown | Roadmap | Rust for Linux | @@ -144,6 +143,40 @@ Medium and Large asks require a **champion** from the team, declared via a `[tea See [Team asks](./team_asks.md) for the full list of recognized ask types. +### Help wanted + +The `## Help wanted` section is optional. Include it if your goal needs a contributor to do the work. It contains a table with three columns: + +```markdown +## Help wanted + +| Task | Experience level | Time investment | +|------|-----------------|-----------------| +| Implement the feature | Intermediate Rust | 3 months part-time | +| Write documentation | Beginner | 2 weeks | +``` + +The presence of this section signals that the goal is looking for someone to step up. Goals that already have all the contributors they need should omit this section entirely. The project goals team will help connect goals with potential contributors. + +### Funding + +The `## Funding` section is optional. Include it if your goal needs financial support to proceed. It contains a table with three columns: + +```markdown +## Funding + +| Purpose | Cost | Status | +|---------|------|--------| +| Contributor (6 months, full-time) | $60,000 | 🔍 Looking | +| Maintenance | $10,000 | 💬 Under discussion | +``` + +The presence of this section signals that the goal is looking for funding. Goals that do not need funding should omit this section entirely. The table rows describe what the funding would cover (purpose), the estimated cost, and the current status. Status values are: + +- 🔍 Looking — actively seeking a funder +- 💬 Under discussion — in conversation with a potential funder +- ✅ Finalized — funding secured + ### Frequently asked questions The `## Frequently asked questions` section is a place for elaboration and discussion. Use it to explain design decisions, answer questions raised during review, and summarize points from the goal discussion. This section typically grows over time as the goal is discussed.