feat: implement lore who — people intelligence commands (5 modes)

Add `lore who` command with 5 query modes answering collaboration questions
using existing DB data (280K notes, 210K discussions, 33K DiffNotes):

- Expert: who knows about a file/directory (DiffNote path analysis + MR breadth scoring)
- Workload: what is a person working on (assigned issues, authored/reviewing MRs, discussions)
- Active: what discussions need attention (unresolved resolvable, global/project-scoped)
- Overlap: who else is touching these files (dual author+reviewer role tracking)
- Reviews: what review patterns does a person have (prefix-based category extraction)

Includes migration 017 (5 composite indexes), CLI skeleton with clap conflicts_with
validation, robot JSON output with input+resolved_input reproducibility, human terminal
output, and 20 unit tests. All quality gates pass.

Closes: bd-1q8z, bd-34rr, bd-2rk9, bd-2ldg, bd-zqpf, bd-s3rc, bd-m7k1, bd-b51e,
bd-2711, bd-1rdi, bd-3mj2, bd-tfh3, bd-zibc, bd-g0d5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-02-07 23:11:14 -05:00
parent 859923f86b
commit f267578aab
9 changed files with 2831 additions and 7 deletions

View File

@@ -193,6 +193,9 @@ pub enum Commands {
/// Show a chronological timeline of events matching a query
Timeline(TimelineArgs),
/// People intelligence: experts, workload, active discussions, overlap
Who(WhoArgs),
#[command(hide = true)]
List {
#[arg(value_parser = ["issues", "mrs"])]
@@ -685,6 +688,56 @@ pub struct TimelineArgs {
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, bounded for output safety)
#[arg(
short = 'n',
long = "limit",
default_value = "20",
value_parser = clap::value_parser!(u16).range(1..=500),
help_heading = "Output"
)]
pub limit: u16,
}
#[derive(Parser)]
pub struct CountArgs {
/// Entity type to count (issues, mrs, discussions, notes, events)