A per-project event log for software work. git log shows commits but
omits design discussions, coding-agent sessions, and decisions that
never produced a commit. worklog captures all of it — automatically,
via git and Claude Code hooks — into markdown files committed under
.worklog/, and renders day/week/month/year reviews from them.
See worklog-design.md for the full design.
# 1. Install the binary
go install github.com/mikethicke/worklog/cmd/worklog@latest
# 2. From inside a git repo:
worklog init # creates .worklog/, installs hooks
export ANTHROPIC_API_KEY=... # optional, enables LLM summaries
worklog sync # imports existing git history
worklog show --week # render this week's eventsThat's it. From here, every commit you make and every Claude Code
session that ends in this repo will be captured into .worklog/. Run
worklog show whenever you want to read back.
Requires Go 1.26+.
go install github.com/mikethicke/worklog/cmd/worklog@latestThis drops a single static binary at $(go env GOPATH)/bin/worklog
(typically ~/go/bin). Make sure that directory is on your $PATH.
Pin to a specific version with @v0.2.0 or track a branch with
@main. To install from a local clone instead:
git clone https://github.com/mikethicke/worklog
cd worklog
go install ./cmd/worklogThen, in each repo where you want a worklog:
cd path/to/your/repo
worklog initworklog init is idempotent and does the following:
- Creates
.worklog/withconfig.yml,reviews/, andbin/. - Writes
.worklog/bin/capture-session, the shim invoked by Claude Code. - Installs
.git/hooks/post-commit(backing up any existing hook topost-commit.pre-worklog). - Adds a
SessionEndentry to.claude/settings.json(preserving any hooks already configured there).
Commit the entire .worklog/ tree and .claude/settings.json so
collaborators get the same capture behavior. On their first Claude
Code session after pulling, Claude Code will prompt them to approve
the new hook — this is expected.
LLM summaries call a configured provider — Anthropic by default,
OpenAI or Gemini optionally (see Summarizer providers
below). For the default provider, set ANTHROPIC_API_KEY in your
shell. Without a key, capture still works — events are written with
deterministic fallback summaries (commit message for git, first user
prompt plus files-touched for Claude sessions). Run
worklog resummarize later to fill those in.
The key can also live in ~/.config/worklog/config.yml, either as a
custom env var name (summarizer.api_key_env) or — if you must —
inline (summarizer.api_key). Prefer the env var form so the key
never lands in a committed file.
Sets up .worklog/, installs the git hook, and registers the Claude
Code SessionEnd hook. Idempotent — safe to re-run.
Reconciler. Walks git history and ~/.claude/projects/<encoded-cwd>/,
writing event files for anything not already captured. Use this on
fresh clones, after a crashed session, or any time you suspect the
hooks missed something. Idempotent — re-running is always safe.
Append a manual entry. With no argument, opens $EDITOR (defaulting
to vi) for a longer note.
worklog note "Decided to drop the OAuth flow in favor of magic links."
worklog note # opens editorAppend an entry of an arbitrary kind. kind must be a lowercase slug
([a-z0-9][a-z0-9-]*) and appears in the event filename and
frontmatter. With no text, opens $EDITOR.
worklog entry decision "Drop OAuth in favor of magic links."
worklog entry incident # opens editor
worklog entry release --tags v1.2.0,prod --refs git:abc1234Flags: --summary, --tags, --refs, --thread, --session-id,
--author, --time (RFC3339).
For scripted / agent-driven entries, pipe a JSON blob over stdin with
--json:
echo '{
"kind": "decision",
"summary": "Drop OAuth in favor of magic links",
"tags": ["auth", "decision"],
"body": "Discussed in standup. Magic links keep us out of GDPR..."
}' | worklog entry --jsonJSON keys: time, end_time, kind, author, refs, summary,
tags, session_id, thread, body. kind is required (or pass it
positionally to override). Flag overrides win over JSON fields.
worklog note is a shortcut for the common kind: note case and is
left intact.
Render a review to stdout. Defaults to --week.
worklog show --day
worklog show --week
worklog show --month
worklog show --year
worklog show --since 2026-05-01 --until 2026-05-07
worklog show --kind commit--day/--week/--month/--year are resolution shortcuts.
--since/--until accept YYYY-MM-DD and override the range.
--kind filters by event kind (commit, claude-session, note,
…).
Generate a clustered, LLM-summarized weekly, monthly, or yearly review. Yearly reviews are composed from the twelve monthly reviews, not from raw events; weekly and monthly reviews are built directly from events in their range.
worklog review --week 2026-W19 # ISO week
worklog review --month 2026-05
worklog review --year 2026
worklog review --month 2026-05 --regenerate # bypass cache, re-run LLMBy default, reviews are persisted to .worklog/reviews/<period>.md
and subsequent runs serve the cached file (this is configurable via
reviews.persist in .worklog/config.yml). Pass --regenerate to
overwrite the cached version with a fresh summarizer pass — useful
after backfilling events into a past period. Commit the persisted
reviews so they're stable and diff-able in PRs.
Fills in any event files whose frontmatter still has summary: pending. Useful after you set up an API key for the first time, or
after a slow batch where capture deferred summarization.
List raw event files. Mostly for debugging.
Delete every captured event and persisted review, leaving config.yml,
bin/capture-session, and README.md intact — i.e. the same state as
just after worklog init. Prompts for confirmation; --force skips
the prompt. After reset, run worklog sync to re-import history.
Hidden — invoked by the git post-commit hook and the Claude Code
SessionEnd hook respectively. You shouldn't need to call these
directly.
worklog reads two files and merges them. The global config at
~/.config/worklog/config.yml sets per-user defaults (your preferred
summarizer model, API key path, attribution, etc.) that apply across
every repo. The per-repo config at .worklog/config.yml (committed)
overrides those defaults for a single project — use it for things the
whole team should share, like the project name and any team-wide git
filters. Built-in defaults fill in anything neither file specifies.
Key fields:
project: webapp
# Attribution for notes and Claude sessions. Optional — if omitted,
# worklog uses your GitHub username (via `gh api user`), falling
# back to your OS user. Setting this trumps both.
author: alice
# Map any author identifier (git name, git email, OS user, GitHub
# login) to a single canonical name. Without this, the same person
# appears as "Alice Smith" on commits and "alice" on Claude sessions.
# Matching is case-insensitive. Best kept in the global config since
# the mapping is per-person, not per-project.
author_aliases:
alice: Alice Smith
alice@example.com: Alice Smith
git:
skip_merges: true
skip_authors: ["dependabot[bot]", "renovate[bot]"]
collapse_fixups: true
claude_code:
enabled: true
store_transcripts: false
summarizer:
provider: anthropic # anthropic | openai | gemini
model: claude-haiku-4-5
api_key_env: ANTHROPIC_API_KEY
reviews:
auto_generate: false
persist: true # write reviews to disk + serve cached on repeatsummarizer.provider selects the LLM backend. Three are built in:
| Provider | Example model |
Default env var |
|---|---|---|
anthropic |
claude-haiku-4-5 |
ANTHROPIC_API_KEY |
openai |
gpt-5-mini |
OPENAI_API_KEY |
gemini |
gemini-2.5-flash |
GEMINI_API_KEY / GOOGLE_API_KEY |
API key resolution order: summarizer.api_key (inline in config) →
the env var named by summarizer.api_key_env → the provider's default
env var from the table above. Unrecognized providers fall back to
deterministic non-LLM summaries.