Daily Standup
A first-class TUI mode (peer to Analysis and Planning) that runs your team's daily scrum — detecting what everyone did since the last standup, estimating sprint progress, and delivering a summary. It can run on a schedule even when yeaboi is closed, so a 09:50 standup lands before your 10:00 call.
Open it from the mode-selection screen (the magenta Standup card) or run it headlessly with --standup-run.
What it does
- Detects recent activity across every configured source — Jira issues, Azure DevOps work items, GitHub commits + PRs, recently-updated Confluence pages, recently-edited Notion pages, and a local git log. Each source is best-effort: an unconfigured or failing source is skipped, never fatal. An authentication failure (401/403) surfaces as a Notice, not silence.
- Asks for your own update first, then infers everyone else. Press Generate and it prompts for your update (Enter to skip); people without a self-report get an inferred summary from a single LLM call.
- Computes sprint day + confidence deterministically (no LLM): which working day of the sprint you're on (weekends and bank holidays excluded), and how actual "Done" points compare to the ideal linear burn-down → On track / At risk / Behind.
- Flags engineering-practice habits deterministically (no LLM) — work that landed with no ticket behind it, a board left out of date, too much held in flight. See Practices.
- Delivers the summary to any combination of terminal, desktop notification, Slack, and email.
- Never shows blank content — if the LLM has no API key or a source returns 401/403, a ⚠ Notices section tells you exactly what to fix.
Practices (engineering-habit signals)
Alongside what each person did, the standup reports a little about how it landed — the process habits that quietly cost a team. The headline case is work with no ticket behind it: a pull request whose branch, title and description name no ticket, and which no work-item link points at, is sprint scope nobody agreed to and nobody can see on the board. Seven rules ship:
- Untracked work / Untracked docs — a change or a documentation page with no ticket reference anywhere.
- Board out of date — a merged pull request whose ticket is still sitting in a not-started column.
- Spread thin — four or more tickets held in progress at once.
- Oversized change — a pull request touching 40+ reviewable files (lockfiles and generated output excluded).
- Bypassed review — three or more commits with no pull request, in a repo where that person did open one.
- Thin commit messages — three or more commits whose subjects name no outcome.
Every rule is deterministic — computed from the collected activity, never written or ranked by the LLM, so the same day's data always produces the same signals. Each one carries the links it was derived from, so a claim can be checked in one click, and the phrasing is a nudge rather than a verdict.
Naming no ticket is not the same as having no ticket
Plenty of real work belongs to a ticket it never mentions — documentation a definition of done asked for, most of all. So before reporting a change as untracked, the standup reads the description, acceptance criteria and definition of done of every ticket the team has open (including long-running ones nobody touched today) and asks whether the change plausibly belongs to one. If it does, the change is dropped and nothing is printed.
Matching can only ever silence a signal, never raise one. That is what makes it safe to be generous: a wrong match costs a missed nudge, while a wrong report costs trust in a message that names a person. It is also why the matched ticket is never shown — you never see the guess, so guessing the wrong sibling ticket in an epic costs nothing at all.
Matching prefers evidence that is hard to produce by accident: the ticket linking the change outright, or a distinctive compound identifier (pipeline-approval) appearing on both sides. Ordinary word overlap only counts against a ticket that person actually holds, is measured against the change's words rather than the ticket's, and ignores anything common across the sprint's tickets — otherwise a definition-of-done checklist copied onto every ticket would match everything to everything. Documentation gets a lower bar, but only against a ticket whose definition of done actually mentions documentation.
Wording gaps are the one thing rules cannot bridge: a ticket saying "customers cannot check out" and a commit saying "fix the cart total rounding" share no vocabulary. When an LLM is configured, a final pass sees only the changes still about to be reported and may excuse further ones — it answers with changes to drop, so it cannot invent, sharpen, or misattribute a report. Switch it off with habit_ai_match; the deterministic rules are unaffected.
Precision is deliberately favoured over recall, because these name a person: a rule fires only on positive evidence and never concludes anything from absence. If the tracker could not be read at all, the whole ticket-shaped family stays silent rather than accusing everyone of not linking tickets. A file list the API truncated counts as unknown, not as "no files". Text that merely looks like a ticket key (UTF-8, SHA-256) only counts when the tracker really produced that project prefix, and a bare #123 only when Azure Boards emitted that work-item id — on GitHub, #123 is a pull request. Signals are capped at three per person.
Detection is on by default and can be switched off or narrowed to a subset of rules per session (habit_detection / habit_rules / habit_ai_match, editable from the TUI or standup_config_set). The team-level rollup on the overview counts members per rule, never signals, so it can't be read as a scoreboard.
When it gets one wrong, say so
It will get some wrong. Open a member's card and press Practices: every signal about that person is listed, and each takes one keystroke — N if it was wrong, Y if it was right. A thumbs-down asks for an optional sentence explaining why.
A thumbs-down does two things. The signal disappears from the report immediately — the stored run is corrected, so exports and re-shares come out clean too. And every change behind it is remembered, so none of them is ever reported for that rule again. That second half is the one that matters day to day: a pull request can sit open for a week, and without it the same wrong nudge would fire at the same person every morning.
The verdict is cast on the signal but remembered per change. So thumbs-downing "PR #42 and 2 other changes carry no ticket reference" excuses three changes rather than silencing the rule for that person — tomorrow the sentence is simply rebuilt from whatever is left. A thumbs-up changes nothing about the report, which was already right; it records the change as a confirmed catch.
Both then feed the LLM matching pass as calibration for how this team draws the line, which is why the note is worth a sentence. It cannot make the standup louder: that pass still answers only with changes to drop, so confirmations make it excuse less and corrections make it excuse more, and neither gives it a way to raise a signal about anyone. The deterministic rules remain the only thing that can.
It works on a shared link too, which is usually where it matters — the person who knows a change belongs to the spike ticket is the teammate reading the report, not you at your terminal. Share Online now serves the standup as a correctable page: each signal carries the same two answers, and a thumbs-down writes straight back to your stored run, so the next person to open the link sees it already gone.
That page is the one export that can talk back, and only that one. Every other shared or written artifact is served with connect-src 'none' — it physically cannot make a request. A correctable standup swaps in one directive, connect-src 'self', and nothing else; the vote goes to the same loopback server behind your tunnel, through the same access code, and calls the same function the TUI does. Anonymized shares are never correctable: the names on that page are masks, so a verdict could not be matched to a member. Anyone holding the link and the code can answer, and the link dies when you close the sharing screen.
Outside the TUI, the same verdict is available as standup_practice_feedback over MCP — so "that one's wrong, it's the spike ticket" works from Claude Code too.
Scheduling (runs when the app is closed)
Press Configure on the Standup page to set the standup time (e.g. 10:00), how many minutes early to run (default 10), weekdays, and delivery channels. You enter when the meeting happens — the job fires a few minutes before (10:00 → runs 09:50), so the summary lands before you start.
Enabling a schedule installs an OS-native job — a launchd agent on macOS (~/Library/LaunchAgents/) or a crontab entry on Linux. On macOS it opens a Terminal at run time and gives you a short, timed window to type your update and confirm (auto-proceeds if you don't respond); on a headless Linux run it just generates and delivers. Under the hood the job runs:
yeaboi --standup-run --standup-interactive --standup-session <id>
No background daemon is kept alive; the operating system fires the job, so it works even with yeaboi fully quit and survives reboots.
Delivery configuration
Non-secret settings (time, channels) live per-session in SQLite. Secrets/creds go in ~/.yeaboi/.env (see .env.example):
- Slack —
SLACK_WEBHOOK_URL(an incoming webhook) - Email —
STANDUP_SMTP_HOST/PORT/USER/PASSWORD,STANDUP_SMTP_SENDER,STANDUP_EMAIL_RECIPIENTS - GitHub activity —
STANDUP_GITHUB_REPO(owner/repo) - Desktop / Terminal — no configuration required
Everything uses the Python standard library (Slack via urllib, email via smtplib, desktop via osascript/notify-send) — no new dependencies.
Exports
Every standup — generated in the TUI, run headlessly, or fired on a schedule — is auto-saved as Markdown and self-contained HTML under ~/.yeaboi/exports/standup/<project>/ (dated standup-YYYY-MM-DD.md / .html), so the output is a shareable document rather than something you reconstruct from logs. The Export button on the page re-writes the latest report on demand, just like the Analysis and Planning pages.
Try it
yeaboi # open the Standup card, press Generate
yeaboi --standup-run --standup-session latest --standup-output terminal
Standup runs are logged to ~/.yeaboi/logs/standup/, exported to ~/.yeaboi/exports/standup/, and persisted to the standup_history table.