# 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.email` plus 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
- `standup` and `standup week`; `--format slack|markdown|plain`; `--tone concise|detailed`; `--copy`
- `--dry-run` shows the exact prompt and an input token estimate; disk cache; `--verbose` cost

## Install

```bash
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

```bash
.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):

```bash
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

```bash
.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".
