---
name: reports-morning-briefing
description: "☕ BRIEFING: Morning catch-up — scan Slack, Jira, Fireflies, Gmail & Google Calendar to produce a scannable briefing of what happened since you last worked, plus a prep rundown of today's meetings with sourced context. Perfect for Monday mornings, after a holiday, or at the start of any workday. Triggers: \"what did I miss\", \"catch me up\", \"morning briefing\", \"what happened while I was away\", \"what's new since Friday\", \"co się działo\", \"co powinienem wiedzieć\", \"zrób mi briefing\", \"poranny briefing\", \"co nowego\", \"nadgonię\", \"co pominąłem\", \"zacznijmy dzień\", \"prepare me for today's meetings\", \"what's on my calendar today\", \"jakie mam dziś spotkania\", or any variation of wanting a situational summary before starting work. Also trigger when someone says \"good morning\", mentions it's Monday, or asks to \"start the day\". Scans all sources automatically — no project required. Output: complete digest of everything important, no fluff, only signal."
---

# Morning Briefing Skill

## Purpose
Produce a scannable "morning coffee" catch-up covering everything important the user needs
to know before starting work. No project scope required — this is a personal digest.

Tone: Direct. Warm but not chatty. Like a trusted colleague summarising things for you.
Length: **As long as it needs to be, as short as it can be.** Every bullet must earn its place.
Format: Scannable sections, bullet points, bold for urgency only.

---

## Step 0: Check for user-specified context (optional)

**Do NOT ask before scanning** — go straight to Step 1.

**Only pause if the user's message explicitly specifies a scope or window** (e.g. "catch me
up since Monday", "only check Slack and Jira", "what happened in the last 3 days").
In that case, use those constraints directly — no confirmation needed.

**If the user says "morning briefing" or similar with no other context** — skip elicitation
entirely and scan everything with the defaults below.

**Optional — only if the user explicitly asks to customise scope:**
Use `ask_user_input_v0` with a single Focus question (multi-select):
- Slack messages & mentions
- Jira tickets
- Meetings (Fireflies)
- Emails (Gmail)
- Today's meetings & prep

Default when no focus is specified: scan all sources.

---

## Step 0.5: Resolve user identity

Resolve the user's identity across tools where needed, but do **not** block unrelated
scans on Fireflies-specific identity lookup. Carry the results into subsequent steps.

- **Slack user ID:** Resolve dynamically by calling `slack_search_users` with the user's
  name or email (obtained from `fireflies_get_user()` or Google Calendar). Store the
  returned `user_id` as `<slack_user_id>` and use it for all `to:<@USERID>` queries.
  If resolution fails, fall back to searching by name only and note the uncertainty.
- **User email (for Fireflies):** Try to resolve by calling `fireflies_get_user()` to get
  the authenticated user's email address. Store this as `<user_email>` for use in the
  Fireflies `participants` filter. If `fireflies_get_user()` fails, fall back to
  inferring the email from today's Google Calendar attendee list (the user's own entry).
  If neither method works, set `<user_email>` as unavailable — skip Fireflies and note
  the limitation at the bottom of the briefing. Do not block other sources.
- **Jira:** `currentUser()` resolves automatically — no action needed.
- **Gmail:** Primary account — no action needed.

Proceed to Step 1 as soon as Slack/Jira/Gmail/Calendar can run. `<user_email>` is
required only to narrow Fireflies by participant — it must not gate the rest of the scan.

---

## Step 1: Scan all sources in parallel

Run all of the following simultaneously. Each source has its own window logic — there is
no single global cutoff date.

### Slack — recent activity & mentions

**Window logic:**
- If today is Monday or the day after a public holiday → use a cutoff date covering roughly the last 3 days
- If the user's message implies a longer absence (e.g. "I'm back from holiday", "what did I miss this week", "I was away since Thursday") → extend the window to cover the full stated absence; if the length is unclear, default to 5 days and note at the bottom that older messages may exist
- Otherwise → use a cutoff date covering roughly the last 1 day
- If user specified an explicit window → use that instead, overriding all of the above

```
slack_search_public_and_private(
  query="to:<@USERID> after:YYYY-MM-DD",
  limit=30, sort="timestamp"
)
```

Also search for high-signal keywords across all channels:
```
slack_search_public_and_private(
  query="urgent OR blocker OR asap OR help after:YYYY-MM-DD",
  limit=20
)
```

For any message where the user was @mentioned or that contains high-signal keywords,
read the full thread — replies often contain the resolution:
```
slack_read_thread(channel_id="<id>", thread_ts="<ts>")
```

Extract: anything requiring a response, decisions made without the user, blockers raised,
key announcements.

**⚠️ MANDATORY before flagging any message as "awaiting response":**
For every candidate message (DM, @mention, keyword hit, or channel message directed at the user),
you MUST verify the user hasn't already replied before including it. Do both checks:
1. `slack_read_thread(channel_id, thread_ts)` — check for a reply in the thread
2. `slack_read_channel(channel_id, limit=20, oldest=<message_ts>)` — read the channel/DM
   forward from that message to catch replies sent outside the thread

Only flag as "awaiting response" if neither check shows a clear on-topic reply from the user.
**Do NOT skip this step.** Flagging a message as unanswered without verifying is a known
failure mode. If the DM channel can't be read, note uncertainty rather than flagging as open.

### Jira — recently updated & newly assigned tickets

**Window logic:** Two queries, both with a 48h window:

```
# Recently updated tickets assigned to the user
JQL: assignee = currentUser()
     AND updated >= "-2d"
     AND status NOT IN (Done, Closed)
     ORDER BY updated DESC
fields: summary, status, priority, issuetype, updated
maxResults: 30
```

```
# Newly assigned tickets (catches tickets assigned but not yet touched)
JQL: assignee = currentUser()
     AND created >= "-2d"
     ORDER BY created DESC
fields: summary, status, priority, issuetype, created
maxResults: 10
```

If user specified a custom window → substitute "-2d" with the appropriate value.

Extract: newly assigned work, meaningful status changes, anything blocked or high-priority,
anything with an approaching deadline. Skip routine "still In Progress, nothing changed" items.

### Fireflies — recent meetings

**Window logic:** Last 7 days. If user specified a shorter window, use that.

```
fireflies_get_transcripts(
  fromDate: "<7-days-ago>",
  limit: 10,
  participants: ["<user_email>"]
)
```

The `participants` filter ensures only meetings the user actually attended are returned —
no post-fetch filtering needed. For each returned meeting, call `fireflies_get_summary`
and extract: decisions made, action items assigned to the user, open questions, anything
requiring follow-up.

### Gmail — unread inbox

**Window logic:** All unread messages in inbox, no date filter.

```
search_threads(
  query="is:unread in:inbox",
  maxResults: 30
)
```

Skim subject lines and senders. Flag: anything from a client, anything with "urgent" /
"action required" / "deadline" in subject, direct replies awaiting response, calendar
invitations requiring a decision.

Ignore: newsletters, automated notifications with no action needed, marketing emails.

**Grouping rule:** Multiple emails of the same low-priority type → collapse into one
grouped bullet. Always collapse the following — never list individually:
- Automated CI/CD notifications (GitHub Actions, Sentry alerts, build results, etc.)
- Calendar accept/decline/maybe replies
- Recruitment or HR platform notifications (Elevato, LinkedIn, Pracuj, etc.)
- Newsletter or marketing emails (even if unread)
- Monitoring or uptime alerts that require no manual response

If unsure whether an email is important, include it as a single collapsed line rather
than a full bullet. When in doubt: *"3 unread notifications from GitHub — no action
needed"* is enough.

---

## Step 1b: Google Calendar — today's meetings

**Always run this step.** Not gated on any user selection.

Fetch all meetings for today:
```
Google Calendar:list_events(
  startTime: "<today>T00:00:00",
  endTime: "<today>T23:59:59",
  timeZone: "<user's local timezone, e.g. Europe/Warsaw>",
  orderBy: "startTime"
)
```

Filter out: all-day events, events the user declined, solo/no-attendee blocks.

For each remaining meeting, note: title, start time, attendees (names preferred over emails).

Then, for each meeting, run these lookups **in parallel** — strictly source-based, no fabrication:

**a) Fireflies — last session with same title or overlapping attendees:**
```
# Primary: search by core keyword across all content
fireflies_get_transcripts(keyword="<core project keyword>", scope="all", limit=3)

# Fallback (if 0 results): search by participant overlap with today's attendees
fireflies_get_transcripts(participants=["<attendee1@email>", "<attendee2@email>"], limit=3)
```
Use the most recent result. Call `fireflies_get_summary` on it. Extract:
- 1-sentence outcome summary
- Any action items assigned to the user that appear unresolved

*Core keyword* = the most distinctive word from the meeting title (e.g. "ProjectX
Refinement" → "ProjectX"). Avoid generic words like "daily", "sync", "meeting".

**b) Jira — open tickets for the implied project:**
Derive a project keyword from the meeting title (e.g. "Acme daily" → Acme,
"ProjectX Refinement" → ProjectX). Search:
```
# Primary: use derived project key
JQL: project = "<derived_key>" AND status NOT IN (Done, Closed)
     AND (assignee = currentUser() OR comment ~ currentUser())
     ORDER BY priority DESC
maxResults: 5

# Fallback (if 0 results or key lookup fails): search by keyword in summary
JQL: assignee = currentUser()
     AND status NOT IN (Done, Closed)
     AND summary ~ "<keyword>"
     ORDER BY priority DESC
maxResults: 5
```
Surface: Blocked tickets, High/Critical priority, items In Review. Skip routine In-Progress.

If no project key can be confidently derived, skip the primary query and go straight to
the fallback. If both return nothing, note *"No Jira context found."* — do not invent.

**c) Slack — recent thread for this project (last 3 days):**
```
slack_search_public_and_private(
  query="<project name> after:<3-days-ago>",
  limit=5, sort="timestamp"
)
```
Extract only: unresolved questions, blockers, or messages directed at the user.

If all three lookups return nothing — show the meeting bare (time + title + attendees)
with note: *"No prior context found."* Do NOT invent talking points or summaries.

---

## Step 2: Synthesise

**Before marking any Slack message as "awaiting response"** — this verification MUST
have already been done during Step 1 scanning (see ⚠️ block above). Do not reach
Step 2 with unverified candidates. If a message slipped through without verification,
run the checks now before including it:
1. The thread under that message: `slack_read_thread(channel_id, thread_ts)`
2. The channel/DM history from that message onwards: `slack_read_channel(channel_id, limit=20, oldest=<message_ts>)`

Only count a subsequent message as a reply if it clearly addresses the sender or
topic of the original (e.g. direct @mention of the sender, or an on-topic response).
An unrelated message posted later is not a reply. When in doubt — omit the item
rather than falsely flagging it as awaiting response.

Cross-reference all sources. Apply this priority filter:

**Include:**
- Action items assigned to the user
- Decisions made that affect the user's work
- Messages / emails awaiting a response from the user
- Ticket changes that are meaningful (new assignment, status shift, blocked, urgent)
- Meetings with relevant outcomes or action items for the user
- Anything with a deadline in the next 48 hours
- Any notable context the user needs before today's meetings

**Exclude:**
- Routine updates with no user impact
- Tickets that changed status but need no action
- Slack noise (emoji reactions, "thanks", casual banter)
- FYI emails with no required action
- Meeting summaries where the user had no action items

**Grouping rule:** Multiple low-priority items of the same type → one grouped bullet.
Example: "4 new (Senior) Delivery Manager applications — Iurii Klova, Magdalena Nowak,
Sebastian Markowicz, Michał Olędzki. Review in Elevato."

**No bullet caps.** Include every item that passes the filter above.
When in doubt about an item's importance: **omit.** Signal only.

---

## Output Format

```
☕ Morning Briefing — [Day, Date]

**🔴 Needs your attention**
• [Urgent item — what it is, who it's from, what's needed]
• [Another item requiring action or response]

**📬 Messages & emails**
• [Slack/email requiring response — from whom, about what]

**📋 Your tickets**
• [PROJ-123] [Title] — [status or why it matters]

**🗣️ While you were away**
• [Meeting name] ([date]) — [1-sentence outcome or action item]

**📅 Coming up today**
• [Deadline or time-sensitive item]

**🗓️ Today's meetings**
[One block per meeting — see format below]

**✅ Proposed to-dos**
• [Short imperative task — sourced from above]
```

---

## Section Rules

**🔴 Needs your attention** — blockers, urgent requests, overdue items, anything explicitly
asking for the user's input. No bullet cap. If nothing urgent: omit this section.

**📬 Messages & emails** — Slack threads or emails awaiting a response. Write as:
`[Person] asked about [topic] in [#channel / email]`. Group batches of similar low-priority
emails into one line. If nothing actionable: omit.

**📋 Your tickets** — only tickets updated or created in the last 48h that changed
meaningfully. Skip routine "still in progress" items. No bullet cap. If nothing: omit.

**🗣️ While you were away** — meetings in the last 7 days where the user was a participant
and outcomes or action items exist. One bullet per meeting, one sentence max.
Skip meetings with no relevant outcomes. Omit section if nothing qualifies.

**📅 Coming up today** — concrete deadlines or time-sensitive items due today or tomorrow.
Omit if nothing found.

**🗓️ Today's meetings** — always shown if there are meetings today. Render one block per
meeting, in chronological order. If no meetings today: omit the section entirely — do
not write "No meetings today."

```
🕙 [HH:MM] [Meeting Title]
👥 [Attendee 1, Attendee 2, + N others]
📝 Last time: [1-sentence Fireflies summary — or "No transcript found."]
✅ Your open items: [unresolved action items from last session — omit if none]
🔴 Blocked ticket: [PROJ-123 title — only if blocked/critical — omit if none]
💬 Slack: [1-line unresolved thread — omit if nothing relevant]
```

Rules:
- **Never invent** summaries, action items, or talking points. Every line traces to a tool result.
- Attendees: use display names, not raw emails. Max 3 named + "+ N others".
- Meeting blocks with no context show bare title/time/attendees + "No prior context found."

**✅ Proposed to-dos** — a consolidated list of concrete actions for the user today,
distilled from everything found in the briefing. Written as short imperative tasks.

Rules:
- Every item must trace directly to a real finding from the scan — no invented tasks
- Format: `[Verb] [object] — [context]`, e.g. "Reply to Mateusz re: deployment blocker in #project-x" or "Review PROJ-123 before standup"
- Max 7 items; if more qualify, group the lowest-priority ones into a single line
- Omit this section only if nothing actionable was found across all sources (rare)
- Do not duplicate items already obvious from 🔴 Needs your attention — use that section for urgent items; use ✅ for the full planned task list including non-urgent items

Sections with nothing to report are **omitted entirely** — never write "Nothing to report."

---

## Scan Caveats

If a source fails or returns no data, add a brief note at the bottom of the briefing:
`*(Gmail not scanned — no access)*`

If Jira returns 0 results for `assignee = currentUser()`, note:
`*(Jira returned no assigned tickets — account may not be linked or no active work found.)*`

If scanning reveals a long absence (5+ days), add one line:
`*(Extended absence — some older items may have been resolved. Check manually if unsure.)*`

---

## Quality Checks

Before presenting, verify:
- [ ] No section says "Nothing to report" — omit instead
- [ ] Every bullet is specific and sourced (no vague "there was activity")
- [ ] Every bullet is genuinely actionable or contextually important — if not, cut it
- [ ] Action items are clearly worded — what to do, not just what happened
- [ ] No duplicates across sections (same item in Slack + Jira → pick one, cross-reference)
- [ ] Low-priority batches are grouped, not listed individually
- [ ] Urgent items lead (🔴 section appears first, or absent if nothing urgent)
- [ ] Meeting prep section: zero fabricated summaries — every line traces to a real tool result
- [ ] Meetings with no context show "No transcript found." — not invented talking points
- [ ] ✅ Proposed to-dos: every item traces to a real finding; nothing invented; max 7 items; no duplicates with 🔴 section

---

## After Presenting

If any items are ambiguous, offer to pull more detail:
*"Want me to pull up [the thread / that ticket / the meeting notes] for more context?"*

Never post, reply, or action anything automatically. Briefing is read-only.