feat(cli): status display/filtering, expanded --fields, and robot-docs --brief
Work item status integration across all CLI output:
Issue listing (lore list issues):
- New Status column appears when any issue has status data, with
hex-color rendering using ANSI 256-color approximation
- New --status flag for case-insensitive filtering (OR logic for
multiple values): lore issues --status "In progress" --status "To do"
- Status fields (name, category, color, icon_name, synced_at) in issue
list query and JSON output with conditional serialization
Issue detail (lore show issue):
- Displays "Status: In progress (in_progress)" with color-coded output
using ANSI 256-color approximation from hex color values
- Status fields included in robot mode JSON with ISO timestamps
- IssueRow, IssueDetail, IssueDetailJson all carry status columns
Robot mode field selection expanded to new commands:
- search: --fields with "minimal" preset (document_id, title, source_type, score)
- timeline: --fields with "minimal" preset (timestamp, type, entity_iid, detail)
- who: --fields with per-mode presets (expert_minimal, workload_minimal, etc.)
- robot-docs: new --brief flag strips response_schema from output (~60% smaller)
- strip_schemas() utility in robot.rs for --brief mode
- expand_fields_preset() extended for search, timeline, and all who modes
Robot-docs manifest updated with --status flag documentation, --fields
flags for search/timeline/who, fields_presets sections, and corrected
search response schema field names.
Note: replaces empty commit dcfd449 which lost staging during hook execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -83,6 +83,11 @@ pub struct IssueDetail {
|
||||
pub milestone: Option<String>,
|
||||
pub closing_merge_requests: Vec<ClosingMrRef>,
|
||||
pub discussions: Vec<DiscussionDetail>,
|
||||
pub status_name: Option<String>,
|
||||
pub status_category: Option<String>,
|
||||
pub status_color: Option<String>,
|
||||
pub status_icon_name: Option<String>,
|
||||
pub status_synced_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -134,6 +139,11 @@ pub fn run_show_issue(
|
||||
milestone: issue.milestone_title,
|
||||
closing_merge_requests: closing_mrs,
|
||||
discussions,
|
||||
status_name: issue.status_name,
|
||||
status_category: issue.status_category,
|
||||
status_color: issue.status_color,
|
||||
status_icon_name: issue.status_icon_name,
|
||||
status_synced_at: issue.status_synced_at,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,6 +160,11 @@ struct IssueRow {
|
||||
project_path: String,
|
||||
due_date: Option<String>,
|
||||
milestone_title: Option<String>,
|
||||
status_name: Option<String>,
|
||||
status_category: Option<String>,
|
||||
status_color: Option<String>,
|
||||
status_icon_name: Option<String>,
|
||||
status_synced_at: Option<i64>,
|
||||
}
|
||||
|
||||
fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Result<IssueRow> {
|
||||
@@ -159,7 +174,9 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
|
||||
(
|
||||
"SELECT i.id, i.iid, i.title, i.description, i.state, i.author_username,
|
||||
i.created_at, i.updated_at, i.web_url, p.path_with_namespace,
|
||||
i.due_date, i.milestone_title
|
||||
i.due_date, i.milestone_title,
|
||||
i.status_name, i.status_category, i.status_color,
|
||||
i.status_icon_name, i.status_synced_at
|
||||
FROM issues i
|
||||
JOIN projects p ON i.project_id = p.id
|
||||
WHERE i.iid = ? AND i.project_id = ?",
|
||||
@@ -169,7 +186,9 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
|
||||
None => (
|
||||
"SELECT i.id, i.iid, i.title, i.description, i.state, i.author_username,
|
||||
i.created_at, i.updated_at, i.web_url, p.path_with_namespace,
|
||||
i.due_date, i.milestone_title
|
||||
i.due_date, i.milestone_title,
|
||||
i.status_name, i.status_category, i.status_color,
|
||||
i.status_icon_name, i.status_synced_at
|
||||
FROM issues i
|
||||
JOIN projects p ON i.project_id = p.id
|
||||
WHERE i.iid = ?",
|
||||
@@ -195,6 +214,11 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
|
||||
project_path: row.get(9)?,
|
||||
due_date: row.get(10)?,
|
||||
milestone_title: row.get(11)?,
|
||||
status_name: row.get(12)?,
|
||||
status_category: row.get(13)?,
|
||||
status_color: row.get(14)?,
|
||||
status_icon_name: row.get(15)?,
|
||||
status_synced_at: row.get(16)?,
|
||||
})
|
||||
})?
|
||||
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
@@ -603,6 +627,17 @@ pub fn print_show_issue(issue: &IssueDetail) {
|
||||
};
|
||||
println!("State: {}", state_styled);
|
||||
|
||||
if let Some(status) = &issue.status_name {
|
||||
let display = match &issue.status_category {
|
||||
Some(cat) => format!("{status} ({})", cat.to_ascii_lowercase()),
|
||||
None => status.clone(),
|
||||
};
|
||||
println!(
|
||||
"Status: {}",
|
||||
style_with_hex(&display, issue.status_color.as_deref())
|
||||
);
|
||||
}
|
||||
|
||||
println!("Author: @{}", issue.author_username);
|
||||
|
||||
if !issue.assignees.is_empty() {
|
||||
@@ -864,6 +899,32 @@ fn print_diff_position(pos: &DiffNotePosition) {
|
||||
}
|
||||
}
|
||||
|
||||
fn style_with_hex<'a>(text: &'a str, hex: Option<&str>) -> console::StyledObject<&'a str> {
|
||||
let styled = console::style(text);
|
||||
let Some(hex) = hex else { return styled };
|
||||
let hex = hex.trim_start_matches('#');
|
||||
if hex.len() != 6 {
|
||||
return styled;
|
||||
}
|
||||
let Ok(r) = u8::from_str_radix(&hex[0..2], 16) else {
|
||||
return styled;
|
||||
};
|
||||
let Ok(g) = u8::from_str_radix(&hex[2..4], 16) else {
|
||||
return styled;
|
||||
};
|
||||
let Ok(b) = u8::from_str_radix(&hex[4..6], 16) else {
|
||||
return styled;
|
||||
};
|
||||
styled.color256(ansi256_from_rgb(r, g, b))
|
||||
}
|
||||
|
||||
fn ansi256_from_rgb(r: u8, g: u8, b: u8) -> u8 {
|
||||
let ri = (u16::from(r) * 5 + 127) / 255;
|
||||
let gi = (u16::from(g) * 5 + 127) / 255;
|
||||
let bi = (u16::from(b) * 5 + 127) / 255;
|
||||
(16 + 36 * ri + 6 * gi + bi) as u8
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct IssueDetailJson {
|
||||
pub id: i64,
|
||||
@@ -882,6 +943,11 @@ pub struct IssueDetailJson {
|
||||
pub milestone: Option<String>,
|
||||
pub closing_merge_requests: Vec<ClosingMrRefJson>,
|
||||
pub discussions: Vec<DiscussionDetailJson>,
|
||||
pub status_name: Option<String>,
|
||||
pub status_category: Option<String>,
|
||||
pub status_color: Option<String>,
|
||||
pub status_icon_name: Option<String>,
|
||||
pub status_synced_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -934,6 +1000,11 @@ impl From<&IssueDetail> for IssueDetailJson {
|
||||
})
|
||||
.collect(),
|
||||
discussions: issue.discussions.iter().map(|d| d.into()).collect(),
|
||||
status_name: issue.status_name.clone(),
|
||||
status_category: issue.status_category.clone(),
|
||||
status_color: issue.status_color.clone(),
|
||||
status_icon_name: issue.status_icon_name.clone(),
|
||||
status_synced_at: issue.status_synced_at.map(ms_to_iso),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1103,6 +1174,12 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ansi256_from_rgb() {
|
||||
assert_eq!(ansi256_from_rgb(0, 0, 0), 16);
|
||||
assert_eq!(ansi256_from_rgb(255, 255, 255), 231);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_issue_assignees_empty() {
|
||||
let conn = setup_test_db();
|
||||
|
||||
Reference in New Issue
Block a user