# Daily Update

> Draft Benjamin's daily Slack update for the Naboo team from Linear. Pulls his issues closed since the previous work day (DONE), currently started (IN PROGRESS), and optionally blocked (BLOCKERS), formats them in the team's Slack style, and iterates in chat until approved. Triggers on "daily update", "daily post", "write my daily", "standup post", or "what did I do yesterday".

- Skill: `benjaming/daily-update` (Agent Skill)
- Install (CLI): `npx skillmds@latest add benjaming/daily-update`
- Raw SKILL.md: https://api.skillmd.com/api/skills/benjaming/daily-update/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: BenjaminG (https://skillmd.com/u/benjaming)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/benjaming/daily-update

---


# Daily Update

Draft Benjamin's `#daily-naboo` Slack post from Linear, in the team's format. Draft-then-confirm by default; post to Slack only when explicitly asked.

## Output format (target)

```
DAILY :
:white_check_mark: DONE :
 <https://linear.app/naboo-team/issue/KEY/slug|KEY: Title>
:rocket: IN PROGRESS:
 <https://linear.app/naboo-team/issue/KEY/slug|KEY: Title>
:x: BLOCKERS:
 <https://linear.app/naboo-team/issue/KEY/slug|KEY: Title>
:hourglass_flowing_sand: TODO:
 <https://linear.app/naboo-team/issue/KEY/slug|KEY: Title>
```

Rules:
- One space before each bullet line (matches team style).
- Omit any section that has zero items.
- Bullets use Slack link syntax `<url|KEY: Title>` so Slack renders one clickable label per line (no unfurl cards).
- If a title contains `|` or `>`, replace them with a space in the label (they break Slack link syntax); keep the full title in the URL.

## Step 1 — Resolve the "yesterday" window

Get the previous working day in ISO (YYYY-MM-DD). Treat Saturday/Sunday as still belonging to Friday.

```bash
DOW=$(date +%u)   # 1=Mon .. 7=Sun
case "$DOW" in
  1) SINCE=$(date -v-3d +%Y-%m-%d) ;;  # Mon → previous Fri
  *) SINCE=$(date -v-1d +%Y-%m-%d) ;;
esac
echo "$SINCE"
```

Echo the window to the user: `"Pulling DONE since <SINCE> and all started issues."`

## Step 2 — Fetch issues from Linear

Run both queries in parallel via the raw GraphQL endpoint (`linear api`). Use the raw endpoint, **not** `linear issue query`, because the CLI's `--json` output does not expose `completedAt` or `parent` — both of which this skill needs. `Bash(linear:*)` already covers `linear api`. `benjamin.gelis@naboo.app` is the assignee (confirm once with `linear auth whoami` if unsure).

**Why `completedAt`, not `updatedAt`:** DONE must filter on `completedAt` (when the issue moved to a completed state), not `updatedAt`. `updatedAt` is bumped by any mutation — a comment, label change, or relation edit — so a long-done issue re-enters the window the moment someone touches it.

```bash
# DONE candidates: completed on/after SINCE (excludes canceled by construction —
# canceled issues carry canceledAt, not completedAt)
linear api 'query($since: DateTimeOrDuration) {
  issues(filter: {
    assignee: { email: { eq: "benjamin.gelis@naboo.app" } },
    state: { type: { eq: "completed" } },
    completedAt: { gte: $since }
  }, first: 50) {
    nodes { identifier title url completedAt state { name } parent { identifier } labels { nodes { name } } }
  }
}' --variable since="$SINCE"

# Currently started (IN PROGRESS + "In Review")
linear api 'query {
  issues(filter: {
    assignee: { email: { eq: "benjamin.gelis@naboo.app" } },
    state: { type: { eq: "started" } }
  }, first: 50) {
    nodes { identifier title url state { name } parent { identifier } labels { nodes { name } } }
  }
}'
```

`$since` accepts `YYYY-MM-DD`. Parse with `jq '.data.issues.nodes[]'`.

### Drop sub-issues

Both queries return `parent` inline. Drop any node where `parent` is non-null — only top-level issues belong in the daily (otherwise a single parent fans out into many sub-issue lines). If a node is missing the `parent` field entirely, keep it (fail open) and note it.

### Filtering rules

- **DONE**: union of
  - all nodes from the completed query (the `completedAt` filter already scopes the window and excludes canceled), and
  - started-query results whose `state.name` matches `/review/i` (team convention: "In Review" items are reported as DONE). "In Review" items have no `completedAt`, so they correctly come from the started query, not the completed one.
- **IN PROGRESS**: started query results **not** matching `/review/i` in `state.name`. Deduplicate against DONE (an issue in both lists stays in DONE only).
- **BLOCKERS**: issues from the started query that carry a `labels` entry matching `/blocked/i` OR are linked as "blocked by" another open issue. If none, skip the section.

If you want to override a bucket (e.g. treat an "In Review" PR that's reverted as still IN PROGRESS), say so in chat — the iteration step handles re-bucketing.

## Step 3 — Draft in chat

Render the post in a fenced block. Above the block, show a one-line summary:
`"<n> done · <m> in progress · <k> blockers · window: <SINCE>"`.

Below the block, list anything the user may want to add manually (non-Linear work like "review PRs", "onboarding session") as a nudge — do not insert these into the draft.

## Step 4 — Iterate

Wait for free-form tweaks. Common ones to support without asking:

- "drop BOF-XXX" → remove that line.
- "move BOF-XXX to done / in progress / blockers" → re-bucket.
- "add blocker: <text>" → append a free-text line under `:x: BLOCKERS:`.
- "shorter titles" → truncate each title to ~60 chars.

Re-render the full block after each change. Never silently drop items.

## Step 5 — Finalize (and optionally post)

When the user confirms ("ok", "lgtm", "copy"), print the final block once more, clean, with no surrounding prose. **Do not post** — Benjamin copies it himself. Confirming the draft is not a post instruction.

Post to Slack **only** when the user explicitly asks ("post it", "tu peux le poster", "c'est bon poste-le", "ship to Slack"). Then call `mcp__slack__conversations_add_message` with:

- `channel_id: C0AMGDXRX53` (the `#daily-naboo` channel)
- `content_type: text/plain` — required, so the `<url|label>` link syntax renders as clickable links instead of being markdown-escaped
- `text:` the exact finalized block shown in chat

After posting, confirm with the channel name (`#daily-naboo`) and stop.

## Notes

- If `linear` is missing or auth fails, abort with the install command from the `linear-cli` skill.
- If both queries return zero issues for Benjamin, say so explicitly and ask whether to draft from yesterday's Slack post (skill does not fetch Slack by default — keep it simple).
- Timezone: the Linear CLI returns UTC. The team posts from Europe/Paris; a UTC cutoff at midnight is close enough — if an issue sits right on the boundary, the user can re-bucket manually.

