Files
gitlore/docs/lore-me-spec.md
teernisse 34680f0087 feat(me): implement lore me personal work dashboard command
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.
2026-02-20 11:09:47 -05:00

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: --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)

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 ...