standup
Writes your daily standup (Yesterday / Today / Blockers) or a weekly manager report from what you actually did. It scans your git repos and an optional checklist file, reduces everything to a compact deterministic digest, and asks Claude Haiku 5.5 only to phrase it. Diffs are never sent.
Features
- Repo discovery under configured roots (depth limit, skips
node_modules, venvs, build dirs), allow/deny globs - Commits across all branches by you (global/local
user.emailplus aliases), numstat diffstat, files touched - Merged PRs inferred from merge commits (
Merge pull request #N,Merge branch) and squash subjects(#N) - Uncommitted work summary from
git status --porcelain - Last-working-day logic: Monday looks back to Friday, weekends and configured holidays are skipped
- Clustering by repo + ticket id (regex, from subject or branch name) else branch; WIP/fixup commits collapsed
- TODO file (
- [ ]/- [x]) diffed against a snapshot saved at the last run standupandstandup week;--format slack|markdown|plain;--tone concise|detailed;--copy--dry-runshows the exact prompt and an input token estimate; disk cache;--verbosecost
Install
cd standup
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev,clipboard]'
export ANTHROPIC_API_KEY=sk-ant-... # only needed for live runs
Quickstart
.venv/bin/standup --dry-run # what would be sent, no key needed
.venv/bin/standup # daily standup (needs API key)
.venv/bin/standup week --format slack --copy
Reproducible demo workspace (two repos, a TODO file, config):
examples/make_demo.sh /tmp/standup-demo
GIT_CONFIG_GLOBAL=/dev/null .venv/bin/standup --config /tmp/standup-demo/config.toml --today 2026-10-16 --dry-run
Usage
standup [day|week] [--config FILE] [--root DIR]... [--todo FILE] [--since YYYY-MM-DD]
[--today YYYY-MM-DD] [--allow GLOB]... [--deny GLOB]... [--format markdown|slack|plain]
[--tone concise|detailed] [--model ID] [--no-paths] [--no-subjects] [--copy]
[--dry-run] [--no-cache] [--verbose]
Dry run (real output, no API key needed)
$ standup --config /tmp/standup-demo/config.toml --today 2026-10-16 --dry-run
...
--- user ---
Git activity digest:
MODE: day
PERIOD: 2026-10-15 to 2026-10-16 (today)
TOTALS: 7 commits in 2 repos, 5 files, +7/-0, 1 merges, 1 active days
REPO billing-api
[PAY-142 branch feature/PAY-142-refunds] 4 commits, 2 wip/fixup collapsed, 2 files, +4/-0
- PAY-142 add refund model
- PAY-142 refund endpoint and validation
paths: src/billing/ (2)
[branch main] 1 commits, 1 files, +1/-0
- Bump retry limit for webhook delivery (#88)
paths: src/billing/
REPO dashboard-web
[UI-7 branch main] 2 commits, 2 files, +2/-0
- UI-7 fix chart tooltip overflow
- UI-7 add tooltip tests
paths: web/ (2)
MERGED:
- billing-api: PR #88: Bump retry limit for webhook delivery
UNCOMMITTED:
- billing-api on main: 0 staged, 0 modified, 1 untracked (src/billing/scratch.py)
POSSIBLE BLOCKERS (from TODO):
- waiting on finance for refund policy sign-off
PLAN CANDIDATES:
- continue PAY-142 in billing-api (branch feature/PAY-142-refunds, 4 commits so far)
- finish uncommitted changes in billing-api (branch main)
- TODO: write refund runbook
- TODO: waiting on finance for refund policy sign-off
--- model: claude-haiku-5-5 | estimated input tokens: ~425 | no API call made ---
Final output (requires a real key; wording below is illustrative of the model's phrasing, the layout is produced by the formatter)
## Yesterday
- Built the refund model and endpoint for PAY-142, including validation
- Raised the webhook retry limit to 5 (PR #88)
- Fixed chart tooltip overflow in dashboard-web and added tests (UI-7)
## Today
- Continue PAY-142 refunds and clean up uncommitted scratch work
- Write the refund runbook
## Blockers
- Waiting on finance for refund policy sign-off
--format slack renders *Yesterday* headings and bullets; --format plain uses YESTERDAY headings and - bullets. standup week adds a title, summary, themes, impact and a "Numbers" section computed in Python (not by the model).
With --verbose a line like [live] input=1000 output=200 cost=$0.000200 goes to stderr; re-runs with identical input print [cached] and cost nothing.
Configuration
~/.config/standup/config.toml (see examples/config.toml):
| key | meaning |
|---|---|
roots |
directories walked for repos (default ~/development) |
max_depth |
levels below a root to search (default 3) |
emails |
alias author emails, in addition to git config user.email and each repo's local email |
holidays |
ISO dates skipped by last-working-day logic |
todo_file |
markdown checklist to diff |
allow / deny |
fnmatch globs on repo name or path |
send_paths / send_subjects |
set false to keep paths or commit subjects out of the prompt |
ticket_regex |
ticket id pattern (default \b[A-Z][A-Z0-9]+-\d+\b) |
model |
default claude-haiku-5-5 (also STANDUP_MODEL / --model) |
Environment: ANTHROPIC_API_KEY, STANDUP_CACHE_DIR (default ~/.cache/standup), STANDUP_STATE_DIR (default ~/.local/state/standup, holds TODO snapshots).
Privacy
Only the digest shown by --dry-run is sent: commit subjects, file paths (grouped by directory), counts, TODO item text. Never diffs or file contents. Use --no-paths, --no-subjects, --deny to reduce it further.
Architecture
src/standup/
config.py config file, identity, cache/state dirs
discovery.py find_repos, allow/deny
workdays.py last_working_day, week_start
gitlog.py git log/status parsing, WIP and merge detection
cluster.py clustering by ticket/branch, path summaries
todo.py checklist parsing, snapshot diff
digest.py builds and renders the digest (the only thing sent)
ai.py prompts, JSON schemas, Haiku call, cache, cost, error mapping
formatters.py markdown/slack/plain rendering
cli.py click entry point
See docs/design.md for algorithms and worked examples.
Tests
.venv/bin/pytest -q # 47 tests, fake Claude client, temp git repos
Limitations
- The model's phrasing was not exercised live here (no API key in the build environment); tests use a fake client.
- Commit date filtering relies on
git log --since, which can stop early on histories with badly out-of-order commit dates. - The window starts at 00:00 of the last working day and runs to now, so today's commits are included.
- The TODO snapshot updates after each successful non-dry run, so a second run the same day shows only newer changes.
- Blockers are only inferred from TODO items containing words like "blocked" or "waiting".