feat(explain): implement lore explain command for auto-generating issue/MR narratives
Adds the full explain command with 7 output sections: entity summary, description, key decisions (heuristic event-note correlation), activity summary, open threads, related entities (closing MRs, cross-references), and timeline excerpt (reuses existing pipeline). Supports --sections filtering, --since time scoping, --no-timeline, --max-decisions, and robot mode JSON output. Closes: bd-2i3z, bd-a3j8, bd-wb0b, bd-3q5e, bd-nj7f, bd-9lbr
This commit is contained in:
@@ -316,6 +316,17 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
||||
"meta": {"elapsed_ms": "int"}
|
||||
}
|
||||
},
|
||||
"explain": {
|
||||
"description": "Auto-generate a structured narrative of an issue or MR",
|
||||
"flags": ["<entity_type: issues|mrs>", "<IID>", "-p/--project <path>", "--sections <comma-list>", "--no-timeline", "--max-decisions <N>", "--since <period>"],
|
||||
"valid_sections": ["entity", "description", "key_decisions", "activity", "open_threads", "related", "timeline"],
|
||||
"example": "lore --robot explain issues 42 --sections key_decisions,activity --since 30d",
|
||||
"response_schema": {
|
||||
"ok": "bool",
|
||||
"data": {"entity": "{type:string, iid:int, title:string, state:string, author:string, assignees:[string], labels:[string], created_at:string, updated_at:string, url:string?, status_name:string?}", "description_excerpt": "string?", "key_decisions": "[{timestamp:string, actor:string, action:string, context_note:string}]?", "activity": "{state_changes:int, label_changes:int, notes:int, first_event:string?, last_event:string?}?", "open_threads": "[{discussion_id:string, started_by:string, started_at:string, note_count:int, last_note_at:string}]?", "related": "{closing_mrs:[{iid:int, title:string, state:string, web_url:string?}], related_issues:[{entity_type:string, iid:int, title:string?, reference_type:string}]}?", "timeline_excerpt": "[{timestamp:string, event_type:string, actor:string?, summary:string}]?"},
|
||||
"meta": {"elapsed_ms": "int"}
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"description": "List notes from discussions with rich filtering",
|
||||
"flags": ["--limit/-n <N>", "--author/-a <username>", "--note-type <type>", "--contains <text>", "--for-issue <iid>", "--for-mr <iid>", "-p/--project <path>", "--since <period>", "--until <period>", "--path <filepath>", "--resolution <any|unresolved|resolved>", "--sort <created|updated>", "--asc", "--include-system", "--note-id <id>", "--gitlab-note-id <id>", "--discussion-id <id>", "--fields <list|minimal>", "--open"],
|
||||
|
||||
@@ -209,6 +209,16 @@ const COMMAND_FLAGS: &[(&str, &[&str])] = &[
|
||||
],
|
||||
),
|
||||
("drift", &["--threshold", "--project"]),
|
||||
(
|
||||
"explain",
|
||||
&[
|
||||
"--project",
|
||||
"--sections",
|
||||
"--no-timeline",
|
||||
"--max-decisions",
|
||||
"--since",
|
||||
],
|
||||
),
|
||||
(
|
||||
"notes",
|
||||
&[
|
||||
@@ -388,6 +398,7 @@ const CANONICAL_SUBCOMMANDS: &[&str] = &[
|
||||
"file-history",
|
||||
"trace",
|
||||
"drift",
|
||||
"explain",
|
||||
"related",
|
||||
"cron",
|
||||
"token",
|
||||
|
||||
1946
src/cli/commands/explain.rs
Normal file
1946
src/cli/commands/explain.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ pub mod cron;
|
||||
pub mod doctor;
|
||||
pub mod drift;
|
||||
pub mod embed;
|
||||
pub mod explain;
|
||||
pub mod file_history;
|
||||
pub mod generate_docs;
|
||||
pub mod ingest;
|
||||
@@ -35,6 +36,7 @@ pub use cron::{
|
||||
pub use doctor::{DoctorChecks, print_doctor_results, run_doctor};
|
||||
pub use drift::{DriftResponse, print_drift_human, print_drift_json, run_drift};
|
||||
pub use embed::{print_embed, print_embed_json, run_embed};
|
||||
pub use explain::{handle_explain, print_explain, print_explain_json, run_explain};
|
||||
pub use file_history::{print_file_history, print_file_history_json, run_file_history};
|
||||
pub use generate_docs::{print_generate_docs, print_generate_docs_json, run_generate_docs};
|
||||
pub use ingest::{
|
||||
|
||||
@@ -277,6 +277,44 @@ pub enum Commands {
|
||||
/// Trace why code was introduced: file -> MR -> issue -> discussion
|
||||
Trace(TraceArgs),
|
||||
|
||||
/// Auto-generate a structured narrative of an issue or MR
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore explain issues 42 # Narrative for issue #42
|
||||
lore explain mrs 99 -p group/repo # Narrative for MR !99 in specific project
|
||||
lore -J explain issues 42 # JSON output for automation
|
||||
lore explain issues 42 --sections key_decisions,open_threads # Specific sections only
|
||||
lore explain issues 42 --since 30d # Narrative scoped to last 30 days
|
||||
lore explain issues 42 --no-timeline # Skip timeline (faster)")]
|
||||
Explain {
|
||||
/// Entity type: "issues" or "mrs" (singular forms also accepted)
|
||||
#[arg(value_parser = ["issues", "mrs", "issue", "mr"])]
|
||||
entity_type: String,
|
||||
|
||||
/// Entity IID
|
||||
iid: i64,
|
||||
|
||||
/// Scope to project (fuzzy match)
|
||||
#[arg(short, long)]
|
||||
project: Option<String>,
|
||||
|
||||
/// Select specific sections (comma-separated)
|
||||
/// Valid: entity, description, key_decisions, activity, open_threads, related, timeline
|
||||
#[arg(long, value_delimiter = ',', help_heading = "Output")]
|
||||
sections: Option<Vec<String>>,
|
||||
|
||||
/// Skip timeline excerpt (faster execution)
|
||||
#[arg(long, help_heading = "Output")]
|
||||
no_timeline: bool,
|
||||
|
||||
/// Maximum key decisions to include
|
||||
#[arg(long, default_value = "10", help_heading = "Output")]
|
||||
max_decisions: usize,
|
||||
|
||||
/// Time scope for events/notes (e.g. 7d, 2w, 1m, or YYYY-MM-DD)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
since: Option<String>,
|
||||
},
|
||||
|
||||
/// Detect discussion divergence from original intent
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore drift issues 42 # Check drift on issue #42
|
||||
|
||||
54
src/main.rs
54
src/main.rs
@@ -13,23 +13,24 @@ use lore::cli::autocorrect::{self, CorrectionResult};
|
||||
use lore::cli::commands::{
|
||||
IngestDisplay, InitInputs, InitOptions, InitResult, ListFilters, MrListFilters,
|
||||
NoteListFilters, RefreshOptions, RefreshResult, SearchCliFilters, SyncOptions, TimelineParams,
|
||||
delete_orphan_projects, open_issue_in_browser, open_mr_in_browser, parse_trace_path,
|
||||
print_count, print_count_json, print_cron_install, print_cron_install_json, print_cron_status,
|
||||
print_cron_status_json, print_cron_uninstall, print_cron_uninstall_json, print_doctor_results,
|
||||
print_drift_human, print_drift_json, print_dry_run_preview, print_dry_run_preview_json,
|
||||
print_embed, print_embed_json, print_event_count, print_event_count_json, print_file_history,
|
||||
print_file_history_json, print_generate_docs, print_generate_docs_json, print_ingest_summary,
|
||||
print_ingest_summary_json, print_list_issues, print_list_issues_json, print_list_mrs,
|
||||
print_list_mrs_json, print_list_notes, print_list_notes_json, print_related_human,
|
||||
print_related_json, print_search_results, print_search_results_json, print_show_issue,
|
||||
print_show_issue_json, print_show_mr, print_show_mr_json, print_stats, print_stats_json,
|
||||
print_sync, print_sync_json, print_sync_status, print_sync_status_json, print_timeline,
|
||||
print_timeline_json_with_meta, print_trace, print_trace_json, print_who_human, print_who_json,
|
||||
query_notes, run_auth_test, run_count, run_count_events, run_cron_install, run_cron_status,
|
||||
run_cron_uninstall, run_doctor, run_drift, run_embed, run_file_history, run_generate_docs,
|
||||
run_ingest, run_ingest_dry_run, run_init, run_init_refresh, run_list_issues, run_list_mrs,
|
||||
run_me, run_related, run_search, run_show_issue, run_show_mr, run_stats, run_sync,
|
||||
run_sync_status, run_timeline, run_token_set, run_token_show, run_who,
|
||||
delete_orphan_projects, handle_explain, open_issue_in_browser, open_mr_in_browser,
|
||||
parse_trace_path, print_count, print_count_json, print_cron_install, print_cron_install_json,
|
||||
print_cron_status, print_cron_status_json, print_cron_uninstall, print_cron_uninstall_json,
|
||||
print_doctor_results, print_drift_human, print_drift_json, print_dry_run_preview,
|
||||
print_dry_run_preview_json, print_embed, print_embed_json, print_event_count,
|
||||
print_event_count_json, print_file_history, print_file_history_json, print_generate_docs,
|
||||
print_generate_docs_json, print_ingest_summary, print_ingest_summary_json, print_list_issues,
|
||||
print_list_issues_json, print_list_mrs, print_list_mrs_json, print_list_notes,
|
||||
print_list_notes_json, print_related_human, print_related_json, print_search_results,
|
||||
print_search_results_json, print_show_issue, print_show_issue_json, print_show_mr,
|
||||
print_show_mr_json, print_stats, print_stats_json, print_sync, print_sync_json,
|
||||
print_sync_status, print_sync_status_json, print_timeline, print_timeline_json_with_meta,
|
||||
print_trace, print_trace_json, print_who_human, print_who_json, query_notes, run_auth_test,
|
||||
run_count, run_count_events, run_cron_install, run_cron_status, run_cron_uninstall, run_doctor,
|
||||
run_drift, run_embed, run_file_history, run_generate_docs, run_ingest, run_ingest_dry_run,
|
||||
run_init, run_init_refresh, run_list_issues, run_list_mrs, run_me, run_related, run_search,
|
||||
run_show_issue, run_show_mr, run_stats, run_sync, run_sync_status, run_timeline, run_token_set,
|
||||
run_token_show, run_who,
|
||||
};
|
||||
use lore::cli::render::{ColorMode, GlyphMode, Icons, LoreRenderer, Theme};
|
||||
use lore::cli::robot::{RobotMeta, strip_schemas};
|
||||
@@ -222,6 +223,25 @@ fn main() {
|
||||
Some(Commands::Trace(args)) => handle_trace(cli.config.as_deref(), args, robot_mode),
|
||||
Some(Commands::Cron(args)) => handle_cron(cli.config.as_deref(), args, robot_mode),
|
||||
Some(Commands::Token(args)) => handle_token(cli.config.as_deref(), args, robot_mode).await,
|
||||
Some(Commands::Explain {
|
||||
entity_type,
|
||||
iid,
|
||||
project,
|
||||
sections,
|
||||
no_timeline,
|
||||
max_decisions,
|
||||
since,
|
||||
}) => handle_explain(
|
||||
cli.config.as_deref(),
|
||||
&entity_type,
|
||||
iid,
|
||||
project.as_deref(),
|
||||
sections,
|
||||
no_timeline,
|
||||
max_decisions,
|
||||
since.as_deref(),
|
||||
robot_mode,
|
||||
),
|
||||
Some(Commands::Drift {
|
||||
entity_type,
|
||||
iid,
|
||||
|
||||
Reference in New Issue
Block a user