Skip to content

Repository files navigation

██████╗ ███████╗██╗   ██╗██╗      ██████╗  ██████╗
██╔══██╗██╔════╝██║   ██║██║     ██╔═══██╗██╔════╝
██║  ██║█████╗  ██║   ██║██║     ██║   ██║██║  ███╗
██║  ██║██╔══╝  ╚██╗ ██╔╝██║     ██║   ██║██║   ██║
██████╔╝███████╗ ╚████╔╝ ███████╗╚██████╔╝╚██████╔╝
╚═════╝ ╚══════╝  ╚═══╝  ╚══════╝ ╚═════╝  ╚═════╝

A development blog that writes itself — through the AI agent you already use.

License: MIT Python 3.11+ Agents: 27


Most developers don't keep a dev log — or they abandon one within a week. But AI coding agents already sit in the loop where the work happens. devlog teaches them, once per project, to narrate that work as they go.

The result: a time-ordered, portfolio-ready record of your project that grows automatically, in the agent's own voice.


Table of contents


Why devlog?

A codebase is the what. The why — the dead ends, the constraint that ruled out the obvious design, the reason a function looks wrong but isn't — lives somewhere far more fragile. With an AI agent it lives in a chat transcript you'll close and never reopen. The commit captures the change, never the reasoning behind it. Six months later that reasoning is simply gone, and the next person — or the next agent — re-derives it from scratch or breaks it by accident.

devlog's real output isn't a blog. It's a durable, git-versioned, cross-agent record of why the code is the way it is — written down at the moment the reasoning is fresh, by the agent that did the reasoning. The portfolio-ready blog is just the human-facing rendering of that record.

The premise holds harder for agent-written code than for hand-written code:

  • The reasoning never lived in your head. You didn't write the code — the agent did. The thinking is in a transcript you're about to discard, not in your memory.
  • Commits carry the what, never the why. Reconstructing intent from git metadata after the fact is lossy by design — and capped by how good the commit message was.
  • Native agent memory can't hold this. It's private, unversioned, and harness-bound — locked to one vendor's tool. It doesn't travel with the repo, survive a switch from one agent to another, or open up to a collaborator.

A dev log usually dies for one reason: the human is the write-trigger, and discipline lapses. devlog moves the trigger to the agent that's already in the loop where the work happens — so the record gets written because the work got done, not because someone remembered to journal.

Capture is best-effort: the agent follows the convention because it's in its context — polite, not guaranteed. For Claude Code, an optional Stop hook makes it far more reliable by nudging the agent before it ends a turn, and devlog status reports when sessions ended without producing an entry — so the blind spot is at least visible.


What you get

A blog/ directory that fills itself in as the agent works — and, underneath it, a project memory that outlives any single session, agent, or vendor.

  • A "why" record that survives. Decisions, tradeoffs, and dead ends get captured in git, in the agent's own voice, as they happen — not reconstructed post-hoc from commit messages.
  • Institutional memory across sessions and agents. .devlog/learned.md accumulates the project's vocabulary, recurring themes, and open threads; /devlog-catchup reloads the project arc, recent work, and glossary at the start of a session. Any agent inherits the context — not just the one that wrote it.
  • Onboarding for humans and agents alike. A new collaborator, or the next agent to touch the repo, reads the narrative instead of re-deriving the reasoning by hand.
  • Durable and versioned. It lives in git and travels with the repo, surviving across agents and harness changes — no vendor, no private store, no lock-in.
  • A portfolio-ready record as a byproduct. Time-ordered, narrative, written for whoever evaluates the work next — collaborators, employers, researchers.
  • Self-maintaining. The tag vocabulary self-updates, the convention can re-tailor its config to the project's domain, and /devlog-manicure audits and prunes stale entries so the record stays trustworthy.

How it compares:

Approach What it produces The catch
devlog Narrative why, written as the work happens Best-effort capture (firmer with the hook)
Commit-driven journals Narrative reconstructed from git after the fact Lossy; quality capped by commit messages
Changelog generators Structured release notes for upgraders Not narrative; wrong audience
Manual markdown diaries Exactly the right narrative You have to remember to write it — and won't

Quickstart

Two commands. No install.

uvx --from git+https://github.com/b3008/devlog.git devlog init
uvx --from git+https://github.com/b3008/devlog.git devlog install --ai claude

Or install it as a uv tool for repeated use:

uv tool install git+https://github.com/b3008/devlog.git
devlog init && devlog install --ai claude

One-time global setup (Claude Code, OpenCode)

Install once and every project gets a blog — no per-project install needed:

devlog install --ai claude --global --with-hook
# or, for OpenCode (hooks are claude-only):
devlog install --ai opencode --global

This injects the convention into the agent's global context file (~/.claude/CLAUDE.md for Claude Code, ~/.config/opencode/AGENTS.md for OpenCode) with self-bootstrapping instructions: the agent creates blog/, .devlog/, and learned.md on its first entry in any project. Per-project customization is still available via devlog init + config edits in any repo.

Running a per-project devlog install on top of a global install injects a thin pointer block instead of duplicating the full convention (which would cost ~1.5k duplicated context tokens per session and let the copies drift). Pass --full to force the standalone convention — useful for repos whose collaborators don't have the global install.


What actually happens

devlog install drops a single, bracketed block into your agent's context file — leaving whatever you already had intact:

  # My Project
  <existing CLAUDE.md content stays untouched>
+
+ <!-- DEVLOG:START - Do not edit manually. Remove with: devlog uninstall --ai <key> -->
+ ## Development Blog (Automatic)
+ After every session where meaningful progress is made, create or
+ update a blog entry in `blog/`.
+ ...triggers, structure, voice, tags, media instructions...
+ <!-- DEVLOG:END -->

From then on, the agent writes entries like this without being asked:

---
type: "Devlog Entry"
title: "Tag vocabulary now self-updates"
date: 2026-04-16
tags: [feature, cli, ux]
description: "devlog install now folds tags from existing entries into the rendered vocabulary."
---

## What changed
...

## Why it matters
...

## How it works
...

## What's next
...

Entries are Open Knowledge Format (OKF) concept documents — plain markdown with YAML frontmatter whose one required field is type. The blog/ directory is an OKF bundle: blog/index.md is the reserved bundle index (stamped with okf_version), and entries cross-link through ordinary markdown links. That makes a devlog readable by any OKF-aware tool or agent, not just this one. Already have a pre-OKF blog? Run devlog migrate — it's automatic on your next devlog install.


Commands

Command What it does
devlog init [--name NAME] Scaffold .devlog/, blog/, blog/media/, blog/index.md, and .devlog/learned.md.
devlog install --ai <key> Inject the convention into the agent's context file. Auto-runs init if needed.
devlog install --ai <key> --global Install into the agent's global config (~/.claude/CLAUDE.md, ~/.config/opencode/AGENTS.md) so the convention applies to every project. Supported: claude, opencode.
devlog install --ai <key> --force Overwrite locally-edited hooks and slash commands with the shipped templates. Untouched files resync on their own; use this to discard an edit you know is obsolete. Never deletes files.
devlog uninstall --ai <key> Remove the convention section and manifest.
devlog uninstall --ai <key> --global Remove the global convention from the agent's global config dir.
devlog index Regenerate blog/index.md from entry frontmatter (newest first).
devlog migrate Bring an existing blog up to Open Knowledge Format conformance (adds type, renames summarydescription, renames the index to index.md, stamps okf_version). Idempotent; --check previews. Also runs automatically during devlog install.
devlog list List all supported agents.
devlog status Show which agents currently have the convention active.
devlog upgrade Upgrade the devlog tool, then resync this repo's convention to it. --check previews; --project-only / --tool-only scope it.
devlog version Print version.

Slash commands (Claude Code, OpenCode)

Installing for Claude Code also drops four slash commands into .claude/commands/ (or ~/.claude/commands/ for --global installs). Installing for OpenCode drops the same four into .opencode/commands/ (globally: ~/.config/opencode/commands/) — the command format (markdown body, description frontmatter, $ARGUMENTS placeholder) is shared. They give you direct, on-demand control over the blog from inside any session — no flag needed, they ship by default.

In a project that hasn't been initialized yet, /devlog-write will bootstrap the scaffolding (.devlog/, blog/, learned.md) on first use — following the global convention's First-time setup instructions or running devlog init if it's available. /devlog-catchup and /devlog-manicure are read-only; they report "no devlog yet" and exit gracefully if the project hasn't been scaffolded.

Command What it does
/devlog-catchup Reads blog/index.md, the 5 most recent entries, and .devlog/learned.md, then returns a structured project briefing — project arc, recent work, open threads, glossary highlights. Use at the start of a session to load context.
/devlog-write <topic> Writes a new entry about the given topic. Computes the next per-day index NN and ISO timestamp, derives a kebab-case slug, follows your project's convention (sections, voice, tags from .devlog/config.yaml), and updates blog/index.md. Refuses vague input rather than fabricating.
/devlog-manicure [topic] Four-phase audit of past entries: categorizes findings (followed-through, revised, discarded, drifted, etc.), writes a recap entry, then proposes wipes or dated blockquote annotations (> **Update YYYY-MM-DD**: …) for you to approve before applying. Optional topic argument scopes the manicure to a single thread.
/devlog-upgrade [flags] Upgrades the devlog tool to the latest from GitHub, then resyncs this repo's convention to it. A thin driver over the two-layer devlog upgrade CLI: it previews with --check, applies when the install is self-upgradeable, and falls back to the right manual step (e.g. git pull for a source checkout) when it isn't. Scope with --tool-only / --project-only.

The first three form a working loop: catchup loads the blog into context, write adds new entries, manicure audits and prunes what's already there. upgrade is maintenance — it pulls the latest devlog and resyncs the convention to it. Uninstall removes them automatically.


Adaptive convention

The convention isn't a frozen snapshot. Three lightweight mechanisms let it grow with the project:

.devlog/learned.md — a shared notebook the agent reads before writing and appends to when durable project knowledge surfaces (domain vocabulary, recurring themes, open threads). It lives outside the injected sentinel block, so accumulation is free and diffable. Humans can edit it too.

Self-updating tag vocabulary — on each devlog install, entries in blog/ are scanned and any tags found in their frontmatter are unioned into the rendered tag list. The agent is told it may introduce new tags when they genuinely fit; the next install folds them into the canonical vocabulary.

Self-tailoring config — if the default triggers or voice don't match the project's domain (say, creative writing rather than a code project), the injected convention instructs the agent to propose edits to config.yaml and prompt the user to re-run install. The tool adapts to the project rather than the other way around.

Together these let the convention absorb what the project has actually been doing — without anyone curating config.yaml by hand.

Optional: runtime enforcement (Claude Code)

The convention asks the agent to self-check at the end of each turn, but agents can interpret rules differently depending on whether the turn produced an artifact. For Claude Code, opt into a hook bundle:

devlog install --ai claude --with-hook

This installs two small scripts under .devlog/hooks/ and merges the matching entries into .claude/settings.json (preserving any existing config):

  • stop.py (Stop) — injects a one-shot reminder before the agent ends its turn, via the structured block channel (no error styling).
  • session_end.py (SessionEnd) — appends one line per session to .devlog/sessions.jsonl, so devlog status can report sessions that ended without producing an entry.

Reinstalls are idempotent and carry existing hooks forward even without the flag (refreshing stale scripts); locally-customized hook scripts are detected by hash and preserved — including across later template changes, since the manifest records what devlog last wrote separately from what is on disk. Pass --force to discard a local edit you know is obsolete; it says which files it overwrote, and never deletes. When both global and per-project hooks are installed, the global instance defers to the project's at runtime — one reminder, one session record. devlog uninstall --ai claude removes the hook entries, deletes the scripts (unless customized), and leaves any unrelated settings untouched.

Is it working?

devlog status reports whether entries are actually being produced — not just whether the sentinel block is present. If the install is more than a day old and no entries have been written, it prints a warning with remediation hints. With the SessionEnd hook installed it also reports session coverage — how many sessions ended since the last entry was written, devlog's blind spot.

Installs are version-aware: the manifest records the devlog version that performed the install, and the sentinel block carries a version stamp (<!-- DEVLOG:START v<version> ... -->). status flags drift — an install from an older version, an unstamped convention block, or artifacts whose recorded hashes differ from the currently shipped templates — and points at devlog install --ai <key>, which doubles as the upgrade command (customized files are preserved). If the install was made by a newer devlog than the one running, it recommends upgrading the tool instead, since a resync would downgrade.

Blog: blog/ — 3 entries, most recent 2026-04-15
Sessions: 12 recorded, last 2026-04-17 — 4 since the last entry

                  Installed Conventions
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Agent       ┃ Context File ┃ Status ┃ Version ┃ Installed  ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩
│ Claude Code │ CLAUDE.md    │ active │ 0.1.0   │ 2026-04-10 │
└─────────────┴──────────────┴────────┴─────────┴────────────┘

Claude Code: installed by devlog 0.1.0, current is 0.2.0. Run
devlog install --ai claude to resync (customized files are preserved).

Supported agents

Four agents get first-class integrations:

Key Agent Context file Extras
claude Claude Code CLAUDE.md slash commands, --global, --with-hook
opencode OpenCode AGENTS.md slash commands, --global
copilot GitHub Copilot .github/copilot-instructions.md
gemini Gemini CLI GEMINI.md
23 more agents use the shared AGENTS.md standard — click to expand

codex · cursor-agent · kimi · qwen · agy · trae · roo · bob · auggie · kilocode · windsurf · codebuddy · vibe · amp · kiro-cli · tabnine · goose · pi · forge · shai · iflow · junie · qodercli

Run devlog list for the live registry.


How it works

  1. init drops a default .devlog/config.yaml, creates the blog skeleton, and scaffolds .devlog/learned.md.
  2. install renders the config into a markdown block and writes it to the agent's context file between sentinel markers. Existing content is preserved; re-installing replaces only the section between the sentinels. A SHA-256 of the resulting file is saved to .devlog/manifests/<agent>.manifest.json.
  3. uninstall strips the sentinel block and removes the manifest.

Layout after install

your-project/
├── .devlog/
│   ├── config.yaml              # stable convention settings
│   ├── learned.md               # agent-maintained project notebook
│   ├── .gitignore               # ignores the runtime session log
│   ├── hooks/                   # Stop + SessionEnd scripts (--with-hook)
│   ├── sessions.jsonl           # session coverage log (gitignored)
│   └── manifests/
│       └── claude.manifest.json # install tracking
├── .claude/                     # claude installs (.opencode/ for opencode)
│   └── commands/                # slash commands
│       ├── devlog-catchup.md
│       ├── devlog-write.md
│       ├── devlog-manicure.md
│       └── devlog-upgrade.md
├── blog/                        # OKF bundle
│   ├── index.md                 # bundle index (okf_version stamp)
│   ├── 2026-04-16-01-first-entry.md
│   └── media/
└── CLAUDE.md                    # convention injected between sentinels

Configuration

Edit .devlog/config.yaml to customize:

Key Purpose
blog_dir / media_dir / index_file Where entries and media live.
sections Headings each entry should have.
voice Tone and audience guidelines baked into the convention.
triggers When the agent should write an entry.
tags Base tag vocabulary (auto-extended by discovery).
frontmatter YAML frontmatter fields for each entry.
media Supporting-artifact instructions (CLI output, diffs, Mermaid, user-provided screenshots). Set enabled: false to skip.

Re-run devlog install --ai <key> after editing to regenerate the injected section.


Upgrading

A devlog install has two layers — the tool itself and the convention it dropped into your repo — and they version independently. devlog upgrade brings both up to date in one step:

devlog upgrade

It detects how the tool was installed, runs the matching upgrade (e.g. uv tool upgrade devlog), then re-invokes the freshly installed binary to resync every agent in this repo — convention block, hooks, and slash commands — preserving any files you've customized. Preview first with --check; scope with --project-only (resync this repo only) or --tool-only (bump the binary only):

devlog upgrade --check          # show what would happen, change nothing
devlog upgrade --project-only   # resync this repo to the installed tool
devlog upgrade --tool-only      # upgrade the binary, skip the resync

If devlog is running from a source checkout or an ephemeral uvx invocation — where there's no managed binary to replace — upgrade won't guess; it prints the exact command to run and stops.

Prefer to do it by hand?
# 1. Upgrade the tool
uv tool upgrade devlog
#    (or, for a clean reinstall from latest:)
# uv tool install --force git+https://github.com/b3008/devlog.git

# 2. Resync this repo's convention to the new tool
devlog install --ai claude        # add --global if it was a global install

devlog status reports the version that performed the install versus the running tool and flags any drift — and if the repo was last touched by a newer devlog than your tool, it tells you to upgrade the tool first rather than resyncing (which would downgrade).


Uninstalling

# Remove from a specific project
devlog uninstall --ai claude

# Remove the global install
devlog uninstall --ai claude --global

This removes the sentinel block from the context file, the Stop hook (if installed) from settings.json, the hook script, and the manifest. Your blog entries, .devlog/config.yaml, and .devlog/learned.md are left untouched — they're your data, not ours.

If the context file (CLAUDE.md, AGENTS.md, etc.) is empty after removing the devlog section, it's deleted automatically.


License

MIT — see LICENSE.

About

Installable development blog convention for AI coding agents

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages