refactor(structure): reorganize codebase into domain-focused modules
This commit is contained in:
876
src/cli/mod.rs
876
src/cli/mod.rs
@@ -1,10 +1,11 @@
|
||||
pub mod args;
|
||||
pub mod autocorrect;
|
||||
pub mod commands;
|
||||
pub mod progress;
|
||||
pub mod render;
|
||||
pub mod robot;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::io::IsTerminal;
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -398,871 +399,8 @@ pub enum Commands {
|
||||
SyncStatus,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore issues -n 10 # List 10 most recently updated issues
|
||||
lore issues -s opened -l bug # Open issues labeled 'bug'
|
||||
lore issues 42 -p group/repo # Show issue #42 in a specific project
|
||||
lore issues --since 7d -a jsmith # Issues updated in last 7 days by jsmith")]
|
||||
pub struct IssuesArgs {
|
||||
/// Issue IID (omit to list, provide to show details)
|
||||
pub iid: Option<i64>,
|
||||
|
||||
/// Maximum results
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "50",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset: iid,title,state,updated_at_iso)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Filter by state (opened, closed, all)
|
||||
#[arg(short = 's', long, help_heading = "Filters", value_parser = ["opened", "closed", "all"])]
|
||||
pub state: Option<String>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(short = 'a', long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by assignee username
|
||||
#[arg(short = 'A', long, help_heading = "Filters")]
|
||||
pub assignee: Option<String>,
|
||||
|
||||
/// Filter by label (repeatable, AND logic)
|
||||
#[arg(short = 'l', long, help_heading = "Filters")]
|
||||
pub label: Option<Vec<String>>,
|
||||
|
||||
/// Filter by milestone title
|
||||
#[arg(short = 'm', long, help_heading = "Filters")]
|
||||
pub milestone: Option<String>,
|
||||
|
||||
/// Filter by work-item status name (repeatable, OR logic)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub status: Vec<String>,
|
||||
|
||||
/// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Filter by due date (before this date, YYYY-MM-DD)
|
||||
#[arg(long = "due-before", help_heading = "Filters")]
|
||||
pub due_before: Option<String>,
|
||||
|
||||
/// Show only issues with a due date
|
||||
#[arg(
|
||||
long = "has-due",
|
||||
help_heading = "Filters",
|
||||
overrides_with = "no_has_due"
|
||||
)]
|
||||
pub has_due: bool,
|
||||
|
||||
#[arg(long = "no-has-due", hide = true, overrides_with = "has_due")]
|
||||
pub no_has_due: bool,
|
||||
|
||||
/// Sort field (updated, created, iid)
|
||||
#[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated", help_heading = "Sorting")]
|
||||
pub sort: String,
|
||||
|
||||
/// Sort ascending (default: descending)
|
||||
#[arg(long, help_heading = "Sorting", overrides_with = "no_asc")]
|
||||
pub asc: bool,
|
||||
|
||||
#[arg(long = "no-asc", hide = true, overrides_with = "asc")]
|
||||
pub no_asc: bool,
|
||||
|
||||
/// Open first matching item in browser
|
||||
#[arg(
|
||||
short = 'o',
|
||||
long,
|
||||
help_heading = "Actions",
|
||||
overrides_with = "no_open"
|
||||
)]
|
||||
pub open: bool,
|
||||
|
||||
#[arg(long = "no-open", hide = true, overrides_with = "open")]
|
||||
pub no_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore mrs -s opened # List open merge requests
|
||||
lore mrs -s merged --since 2w # MRs merged in the last 2 weeks
|
||||
lore mrs 99 -p group/repo # Show MR !99 in a specific project
|
||||
lore mrs -D --reviewer jsmith # Non-draft MRs reviewed by jsmith")]
|
||||
pub struct MrsArgs {
|
||||
/// MR IID (omit to list, provide to show details)
|
||||
pub iid: Option<i64>,
|
||||
|
||||
/// Maximum results
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "50",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset: iid,title,state,updated_at_iso)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Filter by state (opened, merged, closed, locked, all)
|
||||
#[arg(short = 's', long, help_heading = "Filters", value_parser = ["opened", "merged", "closed", "locked", "all"])]
|
||||
pub state: Option<String>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(short = 'a', long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by assignee username
|
||||
#[arg(short = 'A', long, help_heading = "Filters")]
|
||||
pub assignee: Option<String>,
|
||||
|
||||
/// Filter by reviewer username
|
||||
#[arg(short = 'r', long, help_heading = "Filters")]
|
||||
pub reviewer: Option<String>,
|
||||
|
||||
/// Filter by label (repeatable, AND logic)
|
||||
#[arg(short = 'l', long, help_heading = "Filters")]
|
||||
pub label: Option<Vec<String>>,
|
||||
|
||||
/// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Show only draft MRs
|
||||
#[arg(
|
||||
short = 'd',
|
||||
long,
|
||||
conflicts_with = "no_draft",
|
||||
help_heading = "Filters"
|
||||
)]
|
||||
pub draft: bool,
|
||||
|
||||
/// Exclude draft MRs
|
||||
#[arg(
|
||||
short = 'D',
|
||||
long = "no-draft",
|
||||
conflicts_with = "draft",
|
||||
help_heading = "Filters"
|
||||
)]
|
||||
pub no_draft: bool,
|
||||
|
||||
/// Filter by target branch
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub target: Option<String>,
|
||||
|
||||
/// Filter by source branch
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub source: Option<String>,
|
||||
|
||||
/// Sort field (updated, created, iid)
|
||||
#[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated", help_heading = "Sorting")]
|
||||
pub sort: String,
|
||||
|
||||
/// Sort ascending (default: descending)
|
||||
#[arg(long, help_heading = "Sorting", overrides_with = "no_asc")]
|
||||
pub asc: bool,
|
||||
|
||||
#[arg(long = "no-asc", hide = true, overrides_with = "asc")]
|
||||
pub no_asc: bool,
|
||||
|
||||
/// Open first matching item in browser
|
||||
#[arg(
|
||||
short = 'o',
|
||||
long,
|
||||
help_heading = "Actions",
|
||||
overrides_with = "no_open"
|
||||
)]
|
||||
pub open: bool,
|
||||
|
||||
#[arg(long = "no-open", hide = true, overrides_with = "open")]
|
||||
pub no_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore notes # List 50 most recent notes
|
||||
lore notes --author alice --since 7d # Notes by alice in last 7 days
|
||||
lore notes --for-issue 42 -p group/repo # Notes on issue #42
|
||||
lore notes --path src/ --resolution unresolved # Unresolved diff notes in src/")]
|
||||
pub struct NotesArgs {
|
||||
/// Maximum results
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "50",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset: id,author_username,body,created_at_iso)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(short = 'a', long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by note type (DiffNote, DiscussionNote)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub note_type: Option<String>,
|
||||
|
||||
/// Filter by body text (substring match)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub contains: Option<String>,
|
||||
|
||||
/// Filter by internal note ID
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub note_id: Option<i64>,
|
||||
|
||||
/// Filter by GitLab note ID
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub gitlab_note_id: Option<i64>,
|
||||
|
||||
/// Filter by discussion ID
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub discussion_id: Option<String>,
|
||||
|
||||
/// Include system notes (excluded by default)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub include_system: bool,
|
||||
|
||||
/// Filter to notes on a specific issue IID (requires --project or default_project)
|
||||
#[arg(long, conflicts_with = "for_mr", help_heading = "Filters")]
|
||||
pub for_issue: Option<i64>,
|
||||
|
||||
/// Filter to notes on a specific MR IID (requires --project or default_project)
|
||||
#[arg(long, conflicts_with = "for_issue", help_heading = "Filters")]
|
||||
pub for_mr: Option<i64>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Filter until date (YYYY-MM-DD, inclusive end-of-day)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub until: Option<String>,
|
||||
|
||||
/// Filter by file path (exact match or prefix with trailing /)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub path: Option<String>,
|
||||
|
||||
/// Filter by resolution status (any, unresolved, resolved)
|
||||
#[arg(
|
||||
long,
|
||||
value_parser = ["any", "unresolved", "resolved"],
|
||||
help_heading = "Filters"
|
||||
)]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// Sort field (created, updated)
|
||||
#[arg(
|
||||
long,
|
||||
value_parser = ["created", "updated"],
|
||||
default_value = "created",
|
||||
help_heading = "Sorting"
|
||||
)]
|
||||
pub sort: String,
|
||||
|
||||
/// Sort ascending (default: descending)
|
||||
#[arg(long, help_heading = "Sorting")]
|
||||
pub asc: bool,
|
||||
|
||||
/// Open first matching item in browser
|
||||
#[arg(long, help_heading = "Actions")]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct IngestArgs {
|
||||
/// Entity to ingest (issues, mrs). Omit to ingest everything
|
||||
#[arg(value_parser = ["issues", "mrs"])]
|
||||
pub entity: Option<String>,
|
||||
|
||||
/// Filter to single project
|
||||
#[arg(short = 'p', long)]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Override stale sync lock
|
||||
#[arg(short = 'f', long, overrides_with = "no_force")]
|
||||
pub force: bool,
|
||||
|
||||
#[arg(long = "no-force", hide = true, overrides_with = "force")]
|
||||
pub no_force: bool,
|
||||
|
||||
/// Full re-sync: reset cursors and fetch all data from scratch
|
||||
#[arg(long, overrides_with = "no_full")]
|
||||
pub full: bool,
|
||||
|
||||
#[arg(long = "no-full", hide = true, overrides_with = "full")]
|
||||
pub no_full: bool,
|
||||
|
||||
/// Preview what would be synced without making changes
|
||||
#[arg(long, overrides_with = "no_dry_run")]
|
||||
pub dry_run: bool,
|
||||
|
||||
#[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
|
||||
pub no_dry_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore stats # Show document and index statistics
|
||||
lore stats --check # Run integrity checks
|
||||
lore stats --repair --dry-run # Preview what repair would fix
|
||||
lore --robot stats # JSON output for automation")]
|
||||
pub struct StatsArgs {
|
||||
/// Run integrity checks
|
||||
#[arg(long, overrides_with = "no_check")]
|
||||
pub check: bool,
|
||||
|
||||
#[arg(long = "no-check", hide = true, overrides_with = "check")]
|
||||
pub no_check: bool,
|
||||
|
||||
/// Repair integrity issues (auto-enables --check)
|
||||
#[arg(long)]
|
||||
pub repair: bool,
|
||||
|
||||
/// Preview what would be repaired without making changes (requires --repair)
|
||||
#[arg(long, overrides_with = "no_dry_run")]
|
||||
pub dry_run: bool,
|
||||
|
||||
#[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
|
||||
pub no_dry_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore search 'authentication bug' # Hybrid search (default)
|
||||
lore search 'deploy' --mode lexical --type mr # Lexical search, MRs only
|
||||
lore search 'API rate limit' --since 30d # Recent results only
|
||||
lore search 'config' -p group/repo --explain # With ranking explanation")]
|
||||
pub struct SearchArgs {
|
||||
/// Search query string
|
||||
pub query: String,
|
||||
|
||||
/// Search mode (lexical, hybrid, semantic)
|
||||
#[arg(long, default_value = "hybrid", value_parser = ["lexical", "hybrid", "semantic"], help_heading = "Mode")]
|
||||
pub mode: String,
|
||||
|
||||
/// Filter by source type (issue, mr, discussion, note)
|
||||
#[arg(long = "type", value_name = "TYPE", value_parser = ["issue", "mr", "discussion", "note"], help_heading = "Filters")]
|
||||
pub source_type: Option<String>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by label (repeatable, AND logic)
|
||||
#[arg(long, action = clap::ArgAction::Append, help_heading = "Filters")]
|
||||
pub label: Vec<String>,
|
||||
|
||||
/// Filter by file path (trailing / for prefix match)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub path: Option<String>,
|
||||
|
||||
/// Filter by created since (7d, 2w, or YYYY-MM-DD)
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Filter by updated since (7d, 2w, or YYYY-MM-DD)
|
||||
#[arg(long = "updated-since", help_heading = "Filters")]
|
||||
pub updated_since: Option<String>,
|
||||
|
||||
/// Maximum results (default 20, max 100)
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "20",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset: document_id,title,source_type,score)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Show ranking explanation per result
|
||||
#[arg(long, help_heading = "Output", overrides_with = "no_explain")]
|
||||
pub explain: bool,
|
||||
|
||||
#[arg(long = "no-explain", hide = true, overrides_with = "explain")]
|
||||
pub no_explain: bool,
|
||||
|
||||
/// FTS query mode: safe (default) or raw
|
||||
#[arg(long = "fts-mode", default_value = "safe", value_parser = ["safe", "raw"], help_heading = "Mode")]
|
||||
pub fts_mode: String,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore generate-docs # Generate docs for dirty entities
|
||||
lore generate-docs --full # Full rebuild of all documents
|
||||
lore generate-docs --full -p group/repo # Full rebuild for one project")]
|
||||
pub struct GenerateDocsArgs {
|
||||
/// Full rebuild: seed all entities into dirty queue, then drain
|
||||
#[arg(long)]
|
||||
pub full: bool,
|
||||
|
||||
/// Filter to single project
|
||||
#[arg(short = 'p', long)]
|
||||
pub project: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore sync # Full pipeline: ingest + docs + embed
|
||||
lore sync --no-embed # Skip embedding step
|
||||
lore sync --no-status # Skip work-item status enrichment
|
||||
lore sync --full --force # Full re-sync, override stale lock
|
||||
lore sync --dry-run # Preview what would change
|
||||
lore sync --issue 42 -p group/repo # Surgically sync one issue
|
||||
lore sync --mr 10 --mr 20 -p g/r # Surgically sync two MRs")]
|
||||
pub struct SyncArgs {
|
||||
/// Reset cursors, fetch everything
|
||||
#[arg(long, overrides_with = "no_full")]
|
||||
pub full: bool,
|
||||
|
||||
#[arg(long = "no-full", hide = true, overrides_with = "full")]
|
||||
pub no_full: bool,
|
||||
|
||||
/// Override stale lock
|
||||
#[arg(long, overrides_with = "no_force")]
|
||||
pub force: bool,
|
||||
|
||||
#[arg(long = "no-force", hide = true, overrides_with = "force")]
|
||||
pub no_force: bool,
|
||||
|
||||
/// Skip embedding step
|
||||
#[arg(long)]
|
||||
pub no_embed: bool,
|
||||
|
||||
/// Skip document regeneration
|
||||
#[arg(long)]
|
||||
pub no_docs: bool,
|
||||
|
||||
/// Skip resource event fetching (overrides config)
|
||||
#[arg(long = "no-events")]
|
||||
pub no_events: bool,
|
||||
|
||||
/// Skip MR file change fetching (overrides config)
|
||||
#[arg(long = "no-file-changes")]
|
||||
pub no_file_changes: bool,
|
||||
|
||||
/// Skip work-item status enrichment via GraphQL (overrides config)
|
||||
#[arg(long = "no-status")]
|
||||
pub no_status: bool,
|
||||
|
||||
/// Preview what would be synced without making changes
|
||||
#[arg(long, overrides_with = "no_dry_run")]
|
||||
pub dry_run: bool,
|
||||
|
||||
#[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
|
||||
pub no_dry_run: bool,
|
||||
|
||||
/// Show detailed timing breakdown for sync stages
|
||||
#[arg(short = 't', long = "timings")]
|
||||
pub timings: bool,
|
||||
|
||||
/// Acquire file lock before syncing (skip if another sync is running)
|
||||
#[arg(long)]
|
||||
pub lock: bool,
|
||||
|
||||
/// Surgically sync specific issues by IID (repeatable, must be positive)
|
||||
#[arg(long, value_parser = clap::value_parser!(u64).range(1..), action = clap::ArgAction::Append)]
|
||||
pub issue: Vec<u64>,
|
||||
|
||||
/// Surgically sync specific merge requests by IID (repeatable, must be positive)
|
||||
#[arg(long, value_parser = clap::value_parser!(u64).range(1..), action = clap::ArgAction::Append)]
|
||||
pub mr: Vec<u64>,
|
||||
|
||||
/// Scope to a single project (required when --issue or --mr is used)
|
||||
#[arg(short = 'p', long)]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Validate remote entities exist without DB writes (preflight only)
|
||||
#[arg(long)]
|
||||
pub preflight_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore embed # Embed new/changed documents
|
||||
lore embed --full # Re-embed all documents from scratch
|
||||
lore embed --retry-failed # Retry previously failed embeddings")]
|
||||
pub struct EmbedArgs {
|
||||
/// Re-embed all documents (clears existing embeddings first)
|
||||
#[arg(long, overrides_with = "no_full")]
|
||||
pub full: bool,
|
||||
|
||||
#[arg(long = "no-full", hide = true, overrides_with = "full")]
|
||||
pub no_full: bool,
|
||||
|
||||
/// Retry previously failed embeddings
|
||||
#[arg(long, overrides_with = "no_retry_failed")]
|
||||
pub retry_failed: bool,
|
||||
|
||||
#[arg(long = "no-retry-failed", hide = true, overrides_with = "retry_failed")]
|
||||
pub no_retry_failed: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore timeline 'deployment' # Search-based seeding
|
||||
lore timeline issue:42 # Direct: issue #42 and related entities
|
||||
lore timeline i:42 # Shorthand for issue:42
|
||||
lore timeline mr:99 # Direct: MR !99 and related entities
|
||||
lore timeline 'auth' --since 30d -p group/repo # Scoped to project and time
|
||||
lore timeline 'migration' --depth 2 # Deep cross-reference expansion
|
||||
lore timeline 'auth' --no-mentions # Only 'closes' and 'related' edges")]
|
||||
pub struct TimelineArgs {
|
||||
/// Search text or entity reference (issue:N, i:N, mr:N, m:N)
|
||||
pub query: String,
|
||||
|
||||
/// Scope to a specific project (fuzzy match)
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Only show events after this date (e.g. "6m", "2w", "2024-01-01")
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Cross-reference expansion depth (0 = no expansion)
|
||||
#[arg(long, default_value = "1", help_heading = "Expansion")]
|
||||
pub depth: u32,
|
||||
|
||||
/// Skip 'mentioned' edges during expansion (only follow 'closes' and 'related')
|
||||
#[arg(long = "no-mentions", help_heading = "Expansion")]
|
||||
pub no_mentions: bool,
|
||||
|
||||
/// Maximum number of events to display
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "100",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset: timestamp,type,entity_iid,detail)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Maximum seed entities from search
|
||||
#[arg(long = "max-seeds", default_value = "10", help_heading = "Expansion")]
|
||||
pub max_seeds: usize,
|
||||
|
||||
/// Maximum expanded entities via cross-references
|
||||
#[arg(
|
||||
long = "max-entities",
|
||||
default_value = "50",
|
||||
help_heading = "Expansion"
|
||||
)]
|
||||
pub max_entities: usize,
|
||||
|
||||
/// Maximum evidence notes included
|
||||
#[arg(
|
||||
long = "max-evidence",
|
||||
default_value = "10",
|
||||
help_heading = "Expansion"
|
||||
)]
|
||||
pub max_evidence: usize,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore who src/features/auth/ # Who knows about this area?
|
||||
lore who @asmith # What is asmith working on?
|
||||
lore who @asmith --reviews # What review patterns does asmith have?
|
||||
lore who --active # What discussions need attention?
|
||||
lore who --overlap src/features/auth/ # Who else is touching these files?
|
||||
lore who --path README.md # Expert lookup for a root file
|
||||
lore who --path Makefile # Expert lookup for a dotless root file")]
|
||||
pub struct WhoArgs {
|
||||
/// Username or file path (path if contains /)
|
||||
pub target: Option<String>,
|
||||
|
||||
/// Force expert mode for a file/directory path.
|
||||
/// Root files (README.md, LICENSE, Makefile) are treated as exact matches.
|
||||
/// Use a trailing `/` to force directory-prefix matching.
|
||||
#[arg(long, help_heading = "Mode", conflicts_with_all = ["active", "overlap", "reviews"])]
|
||||
pub path: Option<String>,
|
||||
|
||||
/// Show active unresolved discussions
|
||||
#[arg(long, help_heading = "Mode", conflicts_with_all = ["target", "overlap", "reviews", "path"])]
|
||||
pub active: bool,
|
||||
|
||||
/// Find users with MRs/notes touching this file path
|
||||
#[arg(long, help_heading = "Mode", conflicts_with_all = ["target", "active", "reviews", "path"])]
|
||||
pub overlap: Option<String>,
|
||||
|
||||
/// Show review pattern analysis (requires username target)
|
||||
#[arg(long, help_heading = "Mode", requires = "target", conflicts_with_all = ["active", "overlap", "path"])]
|
||||
pub reviews: bool,
|
||||
|
||||
/// Time window (7d, 2w, 6m, YYYY-MM-DD). Default varies by mode.
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Scope to a project (supports fuzzy matching)
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Maximum results per section (1..=500); omit for unlimited
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
value_parser = clap::value_parser!(u16).range(1..=500),
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: Option<u16>,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset; varies by mode)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Show per-MR detail breakdown (expert mode only)
|
||||
#[arg(
|
||||
long,
|
||||
help_heading = "Output",
|
||||
overrides_with = "no_detail",
|
||||
conflicts_with = "explain_score"
|
||||
)]
|
||||
pub detail: bool,
|
||||
|
||||
#[arg(long = "no-detail", hide = true, overrides_with = "detail")]
|
||||
pub no_detail: bool,
|
||||
|
||||
/// Score as if "now" is this date (ISO 8601 or duration like 30d). Expert mode only.
|
||||
#[arg(long = "as-of", help_heading = "Scoring")]
|
||||
pub as_of: Option<String>,
|
||||
|
||||
/// Show per-component score breakdown in output. Expert mode only.
|
||||
#[arg(long = "explain-score", help_heading = "Scoring")]
|
||||
pub explain_score: bool,
|
||||
|
||||
/// Include bot users in results (normally excluded via scoring.excluded_usernames).
|
||||
#[arg(long = "include-bots", help_heading = "Scoring")]
|
||||
pub include_bots: bool,
|
||||
|
||||
/// Include discussions on closed issues and merged/closed MRs
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub include_closed: bool,
|
||||
|
||||
/// Remove the default time window (query all history). Conflicts with --since.
|
||||
#[arg(
|
||||
long = "all-history",
|
||||
help_heading = "Filters",
|
||||
conflicts_with = "since"
|
||||
)]
|
||||
pub all_history: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore me # Full dashboard (default project or all)
|
||||
lore me --issues # Issues section only
|
||||
lore me --mrs # MRs section only
|
||||
lore me --activity # Activity feed only
|
||||
lore me --all # All synced projects
|
||||
lore me --since 2d # Activity window (default: 30d)
|
||||
lore me --project group/repo # Scope to one project
|
||||
lore me --user jdoe # Override configured username")]
|
||||
pub struct MeArgs {
|
||||
/// Show open issues section
|
||||
#[arg(long, help_heading = "Sections")]
|
||||
pub issues: bool,
|
||||
|
||||
/// Show authored + reviewing MRs section
|
||||
#[arg(long, help_heading = "Sections")]
|
||||
pub mrs: bool,
|
||||
|
||||
/// Show activity feed section
|
||||
#[arg(long, help_heading = "Sections")]
|
||||
pub activity: bool,
|
||||
|
||||
/// Show items you're @mentioned in (not assigned/authored/reviewing)
|
||||
#[arg(long, help_heading = "Sections")]
|
||||
pub mentions: bool,
|
||||
|
||||
/// Activity window (e.g. 7d, 2w, 30d). Default: 30d. Only affects activity section.
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// Scope to a project (supports fuzzy matching)
|
||||
#[arg(short = 'p', long, help_heading = "Filters", conflicts_with = "all")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Show all synced projects (overrides default_project)
|
||||
#[arg(long, help_heading = "Filters", conflicts_with = "project")]
|
||||
pub all: bool,
|
||||
|
||||
/// Override configured username
|
||||
#[arg(long = "user", help_heading = "Filters")]
|
||||
pub user: Option<String>,
|
||||
|
||||
/// Select output fields (comma-separated, or 'minimal' preset)
|
||||
#[arg(long, help_heading = "Output", value_delimiter = ',')]
|
||||
pub fields: Option<Vec<String>>,
|
||||
|
||||
/// Reset the since-last-check cursor (next run shows no new events)
|
||||
#[arg(long, help_heading = "Output")]
|
||||
pub reset_cursor: bool,
|
||||
}
|
||||
|
||||
impl MeArgs {
|
||||
/// Returns true if no section flags were passed (show all sections).
|
||||
pub fn show_all_sections(&self) -> bool {
|
||||
!self.issues && !self.mrs && !self.activity && !self.mentions
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore file-history src/main.rs # MRs that touched this file
|
||||
lore file-history src/auth/ -p group/repo # Scoped to project
|
||||
lore file-history src/foo.rs --discussions # Include DiffNote snippets
|
||||
lore file-history src/bar.rs --no-follow-renames # Skip rename chain")]
|
||||
pub struct FileHistoryArgs {
|
||||
/// File path to trace history for
|
||||
pub path: String,
|
||||
|
||||
/// Scope to a specific project (fuzzy match)
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Include discussion snippets from DiffNotes on this file
|
||||
#[arg(long, help_heading = "Output")]
|
||||
pub discussions: bool,
|
||||
|
||||
/// Disable rename chain resolution
|
||||
#[arg(long = "no-follow-renames", help_heading = "Filters")]
|
||||
pub no_follow_renames: bool,
|
||||
|
||||
/// Only show merged MRs
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub merged: bool,
|
||||
|
||||
/// Maximum results
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "50",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore trace src/main.rs # Why was this file changed?
|
||||
lore trace src/auth/ -p group/repo # Scoped to project
|
||||
lore trace src/foo.rs --discussions # Include DiffNote context
|
||||
lore trace src/bar.rs:42 # Line hint (Tier 2 warning)")]
|
||||
pub struct TraceArgs {
|
||||
/// File path to trace (supports :line suffix for future Tier 2)
|
||||
pub path: String,
|
||||
|
||||
/// Scope to a specific project (fuzzy match)
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Include DiffNote discussion snippets
|
||||
#[arg(long, help_heading = "Output")]
|
||||
pub discussions: bool,
|
||||
|
||||
/// Disable rename chain resolution
|
||||
#[arg(long = "no-follow-renames", help_heading = "Filters")]
|
||||
pub no_follow_renames: bool,
|
||||
|
||||
/// Maximum trace chains to display
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "limit",
|
||||
default_value = "20",
|
||||
help_heading = "Output"
|
||||
)]
|
||||
pub limit: usize,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||
lore count issues # Total issues in local database
|
||||
lore count notes --for mr # Notes on merge requests only
|
||||
lore count discussions --for issue # Discussions on issues only")]
|
||||
pub struct CountArgs {
|
||||
/// Entity type to count (issues, mrs, discussions, notes, events)
|
||||
#[arg(value_parser = ["issues", "mrs", "discussions", "notes", "events"])]
|
||||
pub entity: String,
|
||||
|
||||
/// Parent type filter: issue or mr (for discussions/notes)
|
||||
#[arg(short = 'f', long = "for", value_parser = ["issue", "mr"])]
|
||||
pub for_entity: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct CronArgs {
|
||||
#[command(subcommand)]
|
||||
pub action: CronAction,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum CronAction {
|
||||
/// Install cron job for automatic syncing
|
||||
Install {
|
||||
/// Sync interval in minutes (default: 8)
|
||||
#[arg(long, default_value = "8")]
|
||||
interval: u32,
|
||||
},
|
||||
|
||||
/// Remove cron job
|
||||
Uninstall,
|
||||
|
||||
/// Show current cron configuration
|
||||
Status,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct TokenArgs {
|
||||
#[command(subcommand)]
|
||||
pub action: TokenAction,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum TokenAction {
|
||||
/// Store a GitLab token in the config file
|
||||
Set {
|
||||
/// Token value (reads from stdin if omitted in non-interactive mode)
|
||||
#[arg(long)]
|
||||
token: Option<String>,
|
||||
},
|
||||
|
||||
/// Show the current token (masked by default)
|
||||
Show {
|
||||
/// Show the full unmasked token
|
||||
#[arg(long)]
|
||||
unmask: bool,
|
||||
},
|
||||
}
|
||||
pub use args::{
|
||||
CountArgs, CronAction, CronArgs, EmbedArgs, FileHistoryArgs, GenerateDocsArgs, IngestArgs,
|
||||
IssuesArgs, MeArgs, MrsArgs, NotesArgs, SearchArgs, StatsArgs, SyncArgs, TimelineArgs,
|
||||
TokenAction, TokenArgs, TraceArgs, WhoArgs,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user