Complete implementation of the lore me command with:
- Config: gitlab.username field for identity resolution
- CLI: MeArgs with --issues, --mrs, --activity, --since, --project, --all, --user, --fields
- Identity: username resolution with precedence (CLI > config > error)
- Scope: project scope resolution with fuzzy matching and mutual exclusivity
- Types: AttentionState enum (5 states with sort ordering), dashboard structs
- Queries: open issues, authored MRs, reviewing MRs (all with attention state CTEs)
- Activity: 5-source feed (notes, state/label/milestone events, assignment detection)
- Human renderer: summary header, attention legend, section cards, event badges
- Robot renderer: {ok,data,meta} envelope with --fields minimal preset
- Handler: full wiring with section filtering, error paths, exit codes
- Autocorrect: me command flags registered
21 beads (bd-qpk3 through bd-32aw) implemented by 3-agent swarm.
978 tests pass, clippy clean.
15 KiB
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:
--userCLI 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 (matcheswhopattern) - 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:
--allflag shows all synced projects (overrides default_project) - AC-2.7:
--projectand--allare 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 = mewhereis_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):
not_ready: MR only —draft=1AND zero entries inmr_reviewersneeds_attention: Others' latest non-system note > user's latest non-system notestale: 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'renot_started.not_started: User has zero non-system notes on this entity (regardless of whether others have commented)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
--sincewindow, 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
- Human comments (
- "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: truein 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_dividerheaders - 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:
datacontains: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 minimalpreset: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,mrsfor full context - AC-7.9: No limits, no truncation on any array
AC-8: Cross-Project Behavior
- AC-8.1: If
config.default_projectis set, scope to that project by default. If no default project, show all synced projects. - AC-8.2:
--allflag overrides default project and shows all synced projects - AC-8.3:
--projectflag narrows to a specific project (supports fuzzy match like other commands) - AC-8.4:
--projectand--allare 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
--userflag → 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:
--projectand--allboth passed → exit 2 with message
AC-11: Relationship to Existing Commands
- AC-11.1:
who @usernameremains for looking at anyone's workload - AC-11.2:
lore meis 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 asunassignevent type)"requested review from @username"— reviewer assignment (shown asreview_requestevent 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:
--labelflag 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 beneeds_attention, notnot_started.
- Wait: this conflicts with
Corrected logic:
needs_attentiontakes priority overnot_startedwhen others HAVE commented but I haven't. The distinction:not_startedonly 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)
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 ...