feat(me): add mentions section for @-mentions in dashboard
Add a new --mentions flag to the `lore me` command that surfaces items where the user is @-mentioned but NOT already assigned, authoring, or reviewing. This fills an important gap in the personal work dashboard: cross-team requests and callouts that don't show up in the standard issue/MR sections. Implementation details: - query_mentioned_in() scans notes for @username patterns, then filters out entities where the user is already an assignee, author, or reviewer - MentionedInItem type captures entity_type (issue/mr), iid, title, state, project path, attention state, and updated timestamp - Attention state computation marks items as needs_attention when there's recent activity from others - Recency cutoff (7 days) prevents surfacing stale mentions - Both human and robot renderers include the new section The robot mode schema adds mentioned_in array with me_mentions field preset for token-efficient output. Test coverage: - mentioned_in_finds_mention_on_unassigned_issue: basic case - mentioned_in_excludes_assigned_issue: no duplicate surfacing - mentioned_in_excludes_author_on_mr: author already sees in authored MRs - mentioned_in_excludes_reviewer_on_mr: reviewer already sees in reviewing - mentioned_in_uses_recency_cutoff: old mentions filtered - mentioned_in_respects_project_filter: scoping works Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use crate::cli::render::{self, Align, GlyphMode, Icons, LoreRenderer, StyledCell, Table, Theme};
|
||||
|
||||
use super::types::{
|
||||
ActivityEventType, AttentionState, MeActivityEvent, MeDashboard, MeIssue, MeMr, MeSummary,
|
||||
SinceLastCheck,
|
||||
ActivityEventType, AttentionState, MeActivityEvent, MeDashboard, MeIssue, MeMention, MeMr,
|
||||
MeSummary, SinceLastCheck,
|
||||
};
|
||||
|
||||
// ─── Layout Helpers ─────────────────────────────────────────────────────────
|
||||
@@ -164,12 +164,19 @@ pub fn print_summary_header(summary: &MeSummary, username: &str) {
|
||||
Theme::dim().render("0 need attention")
|
||||
};
|
||||
|
||||
let mentioned = if summary.mentioned_in_count > 0 {
|
||||
format!(" {} mentioned", summary.mentioned_in_count)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
println!(
|
||||
" {} projects {} issues {} authored MRs {} reviewing MRs {}",
|
||||
" {} projects {} issues {} authored MRs {} reviewing MRs{} {}",
|
||||
summary.project_count,
|
||||
summary.open_issue_count,
|
||||
summary.authored_mr_count,
|
||||
summary.reviewing_mr_count,
|
||||
mentioned,
|
||||
needs,
|
||||
);
|
||||
|
||||
@@ -342,6 +349,53 @@ pub fn print_reviewing_mrs_section(mrs: &[MeMr], single_project: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Mentioned In Section ────────────────────────────────────────────────
|
||||
|
||||
/// Print the "Mentioned In" section for items where user is @mentioned but
|
||||
/// not assigned, authored, or reviewing.
|
||||
pub fn print_mentioned_in_section(mentions: &[MeMention], single_project: bool) {
|
||||
if mentions.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
render::section_divider(&format!("Mentioned In ({})", mentions.len()))
|
||||
);
|
||||
|
||||
for item in mentions {
|
||||
let attn = styled_attention(&item.attention_state);
|
||||
let ref_str = match item.entity_type.as_str() {
|
||||
"issue" => format!("#{}", item.iid),
|
||||
"mr" => format!("!{}", item.iid),
|
||||
_ => format!("{}:{}", item.entity_type, item.iid),
|
||||
};
|
||||
let ref_style = match item.entity_type.as_str() {
|
||||
"issue" => Theme::issue_ref(),
|
||||
"mr" => Theme::mr_ref(),
|
||||
_ => Theme::bold(),
|
||||
};
|
||||
let state_tag = match item.state.as_str() {
|
||||
"opened" => String::new(),
|
||||
other => format!(" [{}]", other),
|
||||
};
|
||||
let time = render::format_relative_time(item.updated_at);
|
||||
|
||||
println!(
|
||||
" {} {} {}{} {}",
|
||||
attn,
|
||||
ref_style.render(&ref_str),
|
||||
render::truncate(&item.title, title_width(43)),
|
||||
Theme::dim().render(&state_tag),
|
||||
Theme::dim().render(&time),
|
||||
);
|
||||
|
||||
if !single_project {
|
||||
println!(" {}", Theme::dim().render(&item.project_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Activity Feed ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Print the activity feed section (Task #17).
|
||||
@@ -587,6 +641,7 @@ pub fn print_me_dashboard(dashboard: &MeDashboard, single_project: bool) {
|
||||
print_issues_section(&dashboard.open_issues, single_project);
|
||||
print_authored_mrs_section(&dashboard.open_mrs_authored, single_project);
|
||||
print_reviewing_mrs_section(&dashboard.reviewing_mrs, single_project);
|
||||
print_mentioned_in_section(&dashboard.mentioned_in, single_project);
|
||||
print_activity_section(&dashboard.activity, single_project);
|
||||
println!();
|
||||
}
|
||||
@@ -597,6 +652,7 @@ pub fn print_me_dashboard_filtered(
|
||||
single_project: bool,
|
||||
show_issues: bool,
|
||||
show_mrs: bool,
|
||||
show_mentions: bool,
|
||||
show_activity: bool,
|
||||
) {
|
||||
if let Some(ref since) = dashboard.since_last_check {
|
||||
@@ -611,6 +667,9 @@ pub fn print_me_dashboard_filtered(
|
||||
print_authored_mrs_section(&dashboard.open_mrs_authored, single_project);
|
||||
print_reviewing_mrs_section(&dashboard.reviewing_mrs, single_project);
|
||||
}
|
||||
if show_mentions {
|
||||
print_mentioned_in_section(&dashboard.mentioned_in, single_project);
|
||||
}
|
||||
if show_activity {
|
||||
print_activity_section(&dashboard.activity, single_project);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user