feat(me): add lore me personal work dashboard command
Implement a personal work dashboard that shows everything relevant to the
configured GitLab user: open issues assigned to them, MRs they authored,
MRs they are reviewing, and a chronological activity feed.
Design decisions:
- Attention state computed from GitLab interaction data (comments, reviews)
with no local state tracking -- purely derived from existing synced data
- Username resolution: --user flag > config.gitlab.username > actionable error
- Project scoping: --project (fuzzy) | --all | default_project | all
- Section filtering: --issues, --mrs, --activity (combinable, default = all)
- Activity feed controlled by --since (default 30d); work item sections
always show all open items regardless of --since
Architecture (src/cli/commands/me/):
- types.rs: MeDashboard, MeSummary, AttentionState data types
- queries.rs: 4 SQL queries (open_issues, authored_mrs, reviewing_mrs,
activity) using existing issue_assignees, mr_reviewers, notes tables
- render_human.rs: colored terminal output with attention state indicators
- render_robot.rs: {ok, data, meta} JSON envelope with field selection
- mod.rs: orchestration (resolve_username, resolve_project_scope, run_me)
- me_tests.rs: comprehensive unit tests covering all query paths
Config additions:
- New optional gitlab.username field in config.json
- Tests for config with/without username
- Existing test configs updated with username: None
CLI wiring:
- MeArgs struct with section filter, since, project, all, user, fields flags
- Autocorrect support for me command flags
- LoreRenderer::try_get() for safe renderer access in me module
- Robot mode field selection presets (me_items, me_activity)
- handle_me() in main.rs command dispatch
Also fixes duplicate assertions in surgical sync tests (removed 6
duplicate assert! lines that were copy-paste artifacts).
Spec: docs/lore-me-spec.md
This commit is contained in:
290
docs/lore-me-spec.md
Normal file
290
docs/lore-me-spec.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# `lore me` — Personal Work Dashboard
|
||||
|
||||
## Overview
|
||||
|
||||
A personal dashboard command that shows everything relevant to the configured user: open issues, authored MRs, MRs under review, and recent activity. Attention state is computed from GitLab interaction data (comments) with no local state tracking.
|
||||
|
||||
## Command Interface
|
||||
|
||||
```
|
||||
lore me # Full dashboard (default project or all)
|
||||
lore me --issues # Issues section only
|
||||
lore me --mrs # MRs section only (authored + reviewing)
|
||||
lore me --activity # Activity feed only
|
||||
lore me --issues --mrs # Multiple sections (combinable)
|
||||
lore me --all # All synced projects (overrides default_project)
|
||||
lore me --since 2d # Activity window (default: 30d)
|
||||
lore me --project group/repo # Scope to one project
|
||||
lore me --user jdoe # Override configured username
|
||||
```
|
||||
|
||||
Standard global flags: `--robot`/`-J`, `--fields`, `--color`, `--icons`.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### AC-1: Configuration
|
||||
|
||||
- **AC-1.1**: New optional field `gitlab.username` (string) in config.json
|
||||
- **AC-1.2**: Resolution order: `--user` CLI flag > `config.gitlab.username` > exit code 2 with actionable error message suggesting how to set it
|
||||
- **AC-1.3**: Username is case-sensitive (matches GitLab usernames exactly)
|
||||
|
||||
### AC-2: Command Interface
|
||||
|
||||
- **AC-2.1**: New command `lore me` — single command with flags (matches `who` pattern)
|
||||
- **AC-2.2**: Section filter flags: `--issues`, `--mrs`, `--activity` — combinable. Passing multiple shows those sections. No flags = full dashboard (all sections).
|
||||
- **AC-2.3**: `--since <duration>` controls activity feed window, default 30 days. Only affects the activity section; work item sections always show all open items regardless of `--since`.
|
||||
- **AC-2.4**: `--project <path>` scopes to a single project
|
||||
- **AC-2.5**: `--user <username>` overrides configured username
|
||||
- **AC-2.6**: `--all` flag shows all synced projects (overrides default_project)
|
||||
- **AC-2.7**: `--project` and `--all` are mutually exclusive — passing both is exit code 2
|
||||
- **AC-2.8**: Standard global flags: `--robot`/`-J`, `--fields`, `--color`, `--icons`
|
||||
|
||||
### AC-3: "My Items" Definition
|
||||
|
||||
- **AC-3.1**: Issues assigned to me (`issue_assignees.username`). Authorship alone does NOT qualify an issue.
|
||||
- **AC-3.2**: MRs authored by me (`merge_requests.author_username`)
|
||||
- **AC-3.3**: MRs where I'm a reviewer (`mr_reviewers.username`)
|
||||
- **AC-3.4**: Scope is **Assigned (issues) + Authored/Reviewing (MRs)** — no participation/mention expansion
|
||||
- **AC-3.5**: MR assignees (`mr_assignees`) are NOT used — in Pattern 1 workflows (author = assignee), this is redundant with authorship
|
||||
- **AC-3.6**: Activity feed uses CURRENT association only — if you've been unassigned from an issue, activity on it no longer appears. This keeps the query simple and the feed relevant.
|
||||
|
||||
### AC-4: Attention State Model
|
||||
|
||||
- **AC-4.1**: Computed per-item from synced GitLab data, no local state tracking
|
||||
- **AC-4.2**: Interaction signal: notes authored by the user (`notes.author_username = me` where `is_system = 0`)
|
||||
- **AC-4.3**: Future: award emoji will extend interaction signals (separate bead)
|
||||
- **AC-4.4**: States (evaluated in this order — first match wins):
|
||||
1. `not_ready`: MR only — `draft=1` AND zero entries in `mr_reviewers`
|
||||
2. `needs_attention`: Others' latest non-system note > user's latest non-system note
|
||||
3. `stale`: Entity has at least one non-system note from someone, but the most recent note from anyone is older than 30 days. Items with ZERO notes are NOT stale — they're `not_started`.
|
||||
4. `not_started`: User has zero non-system notes on this entity (regardless of whether others have commented)
|
||||
5. `awaiting_response`: User's latest non-system note timestamp >= all others' latest non-system note timestamps (including when user is the only commenter)
|
||||
- **AC-4.5**: Applied to all item types (issues, authored MRs, reviewing MRs)
|
||||
|
||||
### AC-5: Dashboard Sections
|
||||
|
||||
**AC-5.1: Open Issues**
|
||||
- Source: `issue_assignees.username = me`, state = opened
|
||||
- Fields: project path, iid, title, status_name (work item status), attention state, relative time since updated
|
||||
- Sort: attention-first (needs_attention > not_started > awaiting_response > stale), then most recently updated within same state
|
||||
- No limit, no truncation — show all
|
||||
|
||||
**AC-5.2: Open MRs — Authored**
|
||||
- Source: `merge_requests.author_username = me`, state = opened
|
||||
- Fields: project path, iid, title, draft indicator, detailed_merge_status, attention state, relative time
|
||||
- Sort: same as issues
|
||||
|
||||
**AC-5.3: Open MRs — Reviewing**
|
||||
- Source: `mr_reviewers.username = me`, state = opened
|
||||
- Fields: project path, iid, title, MR author username, draft indicator, attention state, relative time
|
||||
- Sort: same as issues
|
||||
|
||||
**AC-5.4: Activity Feed**
|
||||
- Sources (all within `--since` window, default 30d):
|
||||
- Human comments (`notes.is_system = 0`) on my items
|
||||
- State events (`resource_state_events`) on my items
|
||||
- Label events (`resource_label_events`) on my items
|
||||
- Milestone events (`resource_milestone_events`) on my items
|
||||
- Assignment/reviewer system notes (see AC-12 for patterns) on my items
|
||||
- "My items" for the activity feed = items I'm CURRENTLY associated with per AC-3 (current assignment state, not historical)
|
||||
- Includes activity on items regardless of open/closed state
|
||||
- Own actions included but flagged (`is_own: true` in robot, `(you)` suffix + dimmed in human)
|
||||
- Sort: newest first (chronological descending)
|
||||
- No limit, no truncation — show all events
|
||||
|
||||
**AC-5.5: Summary Header**
|
||||
- Counts: projects, open issues, authored MRs, reviewing MRs, needs_attention count
|
||||
- Attention legend (human mode): icon + label for each state
|
||||
|
||||
### AC-6: Human Output — Visual Design
|
||||
|
||||
**AC-6.1: Layout**
|
||||
- Section card style with `section_divider` headers
|
||||
- Legend at top explains attention icons
|
||||
- Two-line per item: main data on line 1, project path on line 2 (indented)
|
||||
- When scoped to single project (`--project`), suppress project path line (redundant)
|
||||
|
||||
**AC-6.2: Attention Icons (three tiers)**
|
||||
|
||||
| State | Nerd Font | Unicode | ASCII | Color |
|
||||
|-------|-----------|---------|-------|-------|
|
||||
| needs_attention | `\uf0f3` bell | `◆` | `[!]` | amber (warning) |
|
||||
| not_started | `\uf005` star | `★` | `[*]` | cyan (info) |
|
||||
| awaiting_response | `\uf017` clock | `◷` | `[~]` | dim (muted) |
|
||||
| stale | `\uf54c` skull | `☠` | `[x]` | dim (muted) |
|
||||
|
||||
**AC-6.3: Color Vocabulary** (matches existing lore palette)
|
||||
- Issue refs (#N): cyan
|
||||
- MR refs (!N): purple
|
||||
- Usernames (@name): cyan
|
||||
- Opened state: green
|
||||
- Merged state: purple
|
||||
- Closed state: dim
|
||||
- Draft indicator: gray
|
||||
- Own actions: dimmed + `(you)` suffix
|
||||
- Timestamps: dim (relative time)
|
||||
|
||||
**AC-6.4: Activity Event Badges**
|
||||
|
||||
| Event | Nerd/Unicode (colored bg) | ASCII fallback |
|
||||
|-------|--------------------------|----------------|
|
||||
| note | cyan bg, dark text | `[note]` cyan text |
|
||||
| status | amber bg, dark text | `[status]` amber text |
|
||||
| label | purple bg, white text | `[label]` purple text |
|
||||
| assign | green bg, dark text | `[assign]` green text |
|
||||
| milestone | magenta bg, white text | `[milestone]` magenta text |
|
||||
|
||||
Fallback: when background colors aren't available (ASCII mode), use colored text with brackets instead of background pills.
|
||||
|
||||
**AC-6.5: Labels**
|
||||
- Human mode: not shown
|
||||
- Robot mode: included in JSON
|
||||
|
||||
### AC-7: Robot Output
|
||||
|
||||
- **AC-7.1**: Standard `{ok, data, meta}` envelope
|
||||
- **AC-7.2**: `data` contains: `username`, `since_iso`, `summary` (counts + `needs_attention_count`), `open_issues[]`, `open_mrs_authored[]`, `reviewing_mrs[]`, `activity[]`
|
||||
- **AC-7.3**: Each item includes: project, iid, title, state, attention_state (programmatic: `needs_attention`, `not_started`, `awaiting_response`, `stale`, `not_ready`), labels, updated_at_iso, web_url
|
||||
- **AC-7.4**: Issues include `status_name` (work item status)
|
||||
- **AC-7.5**: MRs include `draft`, `detailed_merge_status`, `author_username` (reviewing section)
|
||||
- **AC-7.6**: Activity items include: `timestamp_iso`, `event_type`, `entity_type`, `entity_iid`, `project`, `actor`, `is_own`, `summary`, `body_preview` (for notes, truncated to 200 chars)
|
||||
- **AC-7.7**: `--fields minimal` preset: `iid`, `title`, `attention_state`, `updated_at_iso` (work items); `timestamp_iso`, `event_type`, `entity_iid`, `actor` (activity)
|
||||
- **AC-7.8**: Metadata-only depth — agents drill into specific items with `timeline`, `issues`, `mrs` for full context
|
||||
- **AC-7.9**: No limits, no truncation on any array
|
||||
|
||||
### AC-8: Cross-Project Behavior
|
||||
|
||||
- **AC-8.1**: If `config.default_project` is set, scope to that project by default. If no default project, show all synced projects.
|
||||
- **AC-8.2**: `--all` flag overrides default project and shows all synced projects
|
||||
- **AC-8.3**: `--project` flag narrows to a specific project (supports fuzzy match like other commands)
|
||||
- **AC-8.4**: `--project` and `--all` are mutually exclusive (exit 2 if both passed)
|
||||
- **AC-8.5**: Project path shown per-item in both human and robot output (suppressed in human when single-project scoped per AC-6.1)
|
||||
|
||||
### AC-9: Sort Order
|
||||
|
||||
- **AC-9.1**: Work item sections: attention-first, then most recently updated
|
||||
- **AC-9.2**: Attention priority: `needs_attention` > `not_started` > `awaiting_response` > `stale` > `not_ready`
|
||||
- **AC-9.3**: Activity feed: chronological descending (newest first)
|
||||
|
||||
### AC-10: Error Handling
|
||||
|
||||
- **AC-10.1**: No username configured and no `--user` flag → exit 2 with suggestion
|
||||
- **AC-10.2**: No synced data → exit 17 with suggestion to run `lore sync`
|
||||
- **AC-10.3**: Username found but no matching items → empty sections with summary showing zeros
|
||||
- **AC-10.4**: `--project` and `--all` both passed → exit 2 with message
|
||||
|
||||
### AC-11: Relationship to Existing Commands
|
||||
|
||||
- **AC-11.1**: `who @username` remains for looking at anyone's workload
|
||||
- **AC-11.2**: `lore me` is the self-view with attention intelligence
|
||||
- **AC-11.3**: No deprecation of `who` — they serve different purposes
|
||||
|
||||
### AC-12: New Assignments Detection
|
||||
|
||||
- **AC-12.1**: Detect from system notes (`notes.is_system = 1`) matching these body patterns:
|
||||
- `"assigned to @username"` — issue/MR assignment
|
||||
- `"unassigned @username"` — removal (shown as `unassign` event type)
|
||||
- `"requested review from @username"` — reviewer assignment (shown as `review_request` event type)
|
||||
- **AC-12.2**: These appear in the activity feed with appropriate event types
|
||||
- **AC-12.3**: Shows who performed the action (note author from the associated non-system context, or "system" if unavailable) and when (note created_at)
|
||||
- **AC-12.4**: Pattern matching is case-insensitive and matches username at word boundary
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope (Follow-Up Work)
|
||||
|
||||
- **Award emoji sync**: Extends attention signal with reaction timestamps. Requires new table + GitLab REST API integration. Note-level emoji sync has N+1 concern requiring smart batching.
|
||||
- **Participation/mention expansion**: Broadening "my items" beyond assigned+authored.
|
||||
- **Label filtering**: `--label` flag to scope dashboard by label.
|
||||
|
||||
---
|
||||
|
||||
## Design Notes
|
||||
|
||||
### Why No High-Water Mark
|
||||
|
||||
GitLab itself is the source of truth for "what I've engaged with." The attention state is computed by comparing the user's latest comment timestamp against others' latest comment timestamps on each item. No local cursor or mark is needed.
|
||||
|
||||
### Why Comments-Only (For Now)
|
||||
|
||||
Award emoji (reactions) are a valid "I've engaged" signal but aren't currently synced. The attention model is designed to incorporate emoji timestamps when available — adding them later requires no model changes.
|
||||
|
||||
### Why MR Assignees Are Excluded
|
||||
|
||||
GitLab MR workflows have three role fields: Author, Assignee, and Reviewer. In Pattern 1 workflows (the most common post-2020), the author assigns themselves — making assignee redundant with authorship. The Reviewing section uses `mr_reviewers` as the review signal.
|
||||
|
||||
### Attention State Evaluation Order
|
||||
|
||||
States are evaluated in priority order (first match wins):
|
||||
|
||||
```
|
||||
1. not_ready — MR-only: draft=1 AND no reviewers
|
||||
2. needs_attention — others commented after me
|
||||
3. stale — had activity, but nothing in 30d (NOT for zero-comment items)
|
||||
4. not_started — I have zero comments (may or may not have others' comments)
|
||||
5. awaiting_response — I commented last (or I'm the only commenter)
|
||||
```
|
||||
|
||||
Edge cases:
|
||||
- Zero comments from anyone → `not_started` (NOT stale)
|
||||
- Only my comments, none from others → `awaiting_response`
|
||||
- Only others' comments, none from me → `not_started` (I haven't engaged)
|
||||
- Wait: this conflicts with `needs_attention` (step 2). If others have commented and I haven't, then others' latest > my latest (NULL). This should be `needs_attention`, not `not_started`.
|
||||
|
||||
Corrected logic:
|
||||
- `needs_attention` takes priority over `not_started` when others HAVE commented but I haven't. The distinction: `not_started` only applies when NOBODY has commented.
|
||||
|
||||
```
|
||||
1. not_ready — MR-only: draft=1 AND no reviewers
|
||||
2. needs_attention — others have non-system notes AND (I have none OR others' latest > my latest)
|
||||
3. stale — latest note from anyone is older than 30 days
|
||||
4. awaiting_response — my latest >= others' latest (I'm caught up)
|
||||
5. not_started — zero non-system notes from anyone
|
||||
```
|
||||
|
||||
### Attention State Computation (SQL Sketch)
|
||||
|
||||
```sql
|
||||
WITH my_latest AS (
|
||||
SELECT d.issue_id, d.merge_request_id, MAX(n.created_at) AS ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
WHERE n.author_username = ?me AND n.is_system = 0
|
||||
GROUP BY d.issue_id, d.merge_request_id
|
||||
),
|
||||
others_latest AS (
|
||||
SELECT d.issue_id, d.merge_request_id, MAX(n.created_at) AS ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
WHERE n.author_username != ?me AND n.is_system = 0
|
||||
GROUP BY d.issue_id, d.merge_request_id
|
||||
),
|
||||
any_latest AS (
|
||||
SELECT d.issue_id, d.merge_request_id, MAX(n.created_at) AS ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
WHERE n.is_system = 0
|
||||
GROUP BY d.issue_id, d.merge_request_id
|
||||
)
|
||||
SELECT
|
||||
CASE
|
||||
-- MR-only: draft with no reviewers
|
||||
WHEN entity_type = 'mr' AND draft = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM mr_reviewers WHERE merge_request_id = entity_id)
|
||||
THEN 'not_ready'
|
||||
-- Others commented and I haven't caught up (or never engaged)
|
||||
WHEN others.ts IS NOT NULL AND (my.ts IS NULL OR others.ts > my.ts)
|
||||
THEN 'needs_attention'
|
||||
-- Had activity but gone quiet for 30d
|
||||
WHEN any.ts IS NOT NULL AND any.ts < ?now_minus_30d
|
||||
THEN 'stale'
|
||||
-- I've responded and I'm caught up
|
||||
WHEN my.ts IS NOT NULL AND my.ts >= COALESCE(others.ts, 0)
|
||||
THEN 'awaiting_response'
|
||||
-- Nobody has commented at all
|
||||
ELSE 'not_started'
|
||||
END AS attention_state
|
||||
FROM ...
|
||||
```
|
||||
Reference in New Issue
Block a user