Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 97 additions & 100 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
# Shift Log

[![License](https://img.shields.io/badge/License-AINAL-blue.svg)](https://github.com/re-cinq/ai-native-application-license)
[![Go](https://img.shields.io/badge/Go-1.21+-00ADD8.svg)](https://go.dev)
[![Git Notes](https://img.shields.io/badge/Git%20Notes-Supported-green.svg)](https://git-scm.com/docs/git-notes)

Save, view, and search coding agent conversation history, persisted as Git Notes.

## Install
## ✨ Features

- πŸ“ **Automatic Capture** β€” Conversations saved automatically on commits
- πŸ” **Full-text Search** β€” Search through past agent sessions
- πŸ“Š **Summarise** β€” AI-powered conversation summaries
- πŸ”„ **Resume Sessions** β€” Pick up where you left off
- 🌐 **Web Viewer** β€” Visualize conversations in browser
- 🀝 **Team Sync** β€” Share conversations across your team via Git

## πŸš€ Quick Start

### Installation

```bash
curl -fsSL https://raw.githubusercontent.com/re-cinq/shift-log/master/scripts/install.sh | bash
```

### Initialize in a Git Repo

```bash
# ...In a Git repo
cd your-project
shiftlog init --agent=<agent>
```

Where `<agent>` is `claude` (default), `codex`, `copilot`, `gemini`, or `opencode`.

Now work with your coding agent as you would normally. Whenever you or the agent commit, the conversation since the last commit will be attached to that commit as a Git Note.
Now work with your coding agent as normal. Whenever you or the agent commits, the conversation since the last commit will be attached as a Git Note.

## Supported Agents
## πŸ“‹ Supported Agents

| Agent | Init command | How it hooks in |
| ----------- | ------------------------------- | ------------------------------------- |
| Claude Code | `shiftlog init` (default) | `.claude/settings.json` hooks |
| Codex CLI | `shiftlog init --agent=codex` | Post-commit git hook |
| Copilot CLI | `shiftlog init --agent=copilot` | `.github/hooks/shiftlog.json` hook |
| Gemini CLI | `shiftlog init --agent=gemini` | `.gemini/settings.json` hooks |
| OpenCode | `shiftlog init --agent=opencode` | `.opencode/plugins/shiftlog.js` plugin |
| Agent | Init Command | Hook Method |
|-------|--------------|-------------|
| **Claude Code** | `shiftlog init` (default) | `.claude/settings.json` |
| **Codex CLI** | `shiftlog init --agent=codex` | Post-commit hook |
| **Copilot CLI** | `shiftlog init --agent=copilot` | `.github/hooks/shiftlog.json` |
| **Gemini CLI** | `shiftlog init --agent=gemini` | `.gemini/settings.json` |
| **OpenCode** | `shiftlog init --agent=opencode` | `.opencode/plugins/shiftlog.js` |

## Usage
## πŸ’‘ Usage

**See what conversations you have:**
**See your conversation history:**

```bash
shiftlog list
Expand All @@ -38,17 +55,17 @@ shiftlog list
**Search through past conversations:**

```bash
shiftlog search "authentication" # Text search
shiftlog search --agent claude --branch main # Filter by metadata
shiftlog search "jwt" --regex --context 2 # Regex with context lines
shiftlog search "authentication" # Text search
shiftlog search --agent claude --branch main # Filter by metadata
shiftlog search "jwt" --regex --context 2 # Regex with context
```

**Get a quick summary of a conversation:**
**Summarise a conversation:**

```bash
shiftlog summarise # Summarise HEAD conversation
shiftlog tldr HEAD~1 # Alias, works the same way
shiftlog tldr --focus="security" # Prioritise security-related changes
shiftlog summarise # Summarise HEAD conversation
shiftlog tldr HEAD~1 # Alias, same result
shiftlog tldr --focus="security"
```

**Resume a past session:**
Expand All @@ -58,118 +75,98 @@ shiftlog resume abc123 # By commit SHA
shiftlog resume HEAD~3 # By git ref
```

**View in your browser:**
**View in browser:**

```bash
shiftlog serve
```

**Pull down conversations from a repo you cloned:**
**Sync with remote:**

```bash
shiftlog sync pull
shiftlog sync push
```

## shiftlog vs Entire

| | [Entire](https://entire.io) | shiftlog |
| ------------------- | --------------------------- | ---------------------------------------------------------- |
| **Funding** | $60M seed round | Claude Code Max plan ($200/mo) |
| **Staffing** | 12 engineers | An imbecile spec-driving while not really paying attention |
| **Agents** | Claude Code, Gemini CLI | Claude Code, Codex CLI, Copilot CLI, Gemini CLI, OpenCode |
| **Storage** | Custom checkpoints format | Standard Git Notes |
| **Resume sessions** | No | Yes |
| **Web viewer** | No | Yes |
| **Rebase support** | No | Yes |
| **Open source** | Yes | Yes |

## Why?

In order to understand _how_ and _why_ a commit was made, we need to see the conversation that led to it.

## How It Works

Shiftlog uses [Git Notes](https://git-scm.com/docs/git-notes) to attach conversations to commits, stored under `refs/notes/shiftlog` to keep `git log` clean. When you run `shiftlog init`, it sets up hooks so:

1. When the coding agent makes a commit, the conversation is saved automatically
2. When you make a commit during an agent session, it's saved too
3. When you push/pull, conversations sync with the remote

No extra steps needed during your normal workflow.

To view notes directly with git: `git log --notes=shiftlog`

## Commands
## βš™οΈ Commands Reference

| Command | Description |
| ------------------------- | --------------------------------------- |
| `shiftlog init` | Initialize shiftlog in the current repo |
| `shiftlog list` | List commits with stored conversations |
| `shiftlog search [query]` | Search through stored conversations |
| `shiftlog show [ref]` | Show conversation history for a commit |
| `shiftlog summarise [ref]` | Summarise a conversation using your coding agent |
| `shiftlog resume <commit>` | Resume a coding agent session from a commit |
| `shiftlog serve` | Start the web visualization server |
| `shiftlog doctor` | Diagnose shiftlog configuration issues |
| `shiftlog debug` | Toggle debug logging |
| `shiftlog sync push/pull` | Sync conversation notes with remote |
| `shiftlog remap` | Remap orphaned notes to rebased commits |
| Command | Description |
|---------|-------------|
| `shiftlog init` | Initialize shiftlog in the current repo |
| `shiftlog list` | List commits with stored conversations |
| `shiftlog search [query]` | Search through stored conversations |
| `shiftlog show [ref]` | Show conversation history for a commit |
| `shiftlog summarise [ref]` | Summarise a conversation |
| `shiftlog resume <commit>` | Resume a coding agent session |
| `shiftlog serve` | Start the web visualization server |
| `shiftlog doctor` | Diagnose configuration issues |
| `shiftlog sync push/pull` | Sync notes with remote |
| `shiftlog remap` | Remap orphaned notes after rebase |

## Requirements
## πŸ”§ Requirements

- Git
- One of the supported coding agents (Claude Code, Codex CLI, Copilot CLI, Gemini CLI, or OpenCode)
- One of: Claude Code, Codex CLI, Copilot CLI, Gemini CLI, or OpenCode

## Multi-Developer Sync
## πŸ€– How It Works

When multiple developers use shiftlog on the same repository, each person's conversation notes are synced automatically via git push/pull hooks. Notes from different developers are merged seamlessly because they typically annotate different commits.
Shiftlog uses [Git Notes](https://git-scm.com/docs/git-notes) to attach conversations to commits under `refs/notes/shiftlog`. This keeps `git log` clean while persisting valuable context.

If the remote notes ref has diverged (e.g. two developers pushed notes without pulling first), `shiftlog sync push` will reject the push and advise you to pull first:

```bash
shiftlog sync pull # Fetches and merges remote notes
shiftlog sync push # Now succeeds
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” commit β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Claude Code β”‚ ──────────────▢│ Git β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ post-commit hook β”‚ git notes
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ shiftlog β”‚ ──────────────▢│ Git Note β”‚
β”‚ capture β”‚ β”‚ refs/notes/ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ shiftlog β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

In the rare case where two developers annotate the exact same commit SHA, both notes are preserved by concatenation β€” no data is lost.

## Git Worktrees

Shiftlog is worktree-safe. If you use `git worktree` to work on multiple branches simultaneously, each worktree sees only the conversations for commits on its own branch. Hooks are shared across worktrees (as git requires), but `shiftlog list` and `shiftlog show` are scoped to the current HEAD.

## Local Rebase
## πŸ‘₯ Team Collaboration

Conversation notes automatically follow commits when you rebase. During `shiftlog init`, the `notes.rewriteRef` git config is set to `refs/notes/shiftlog`, which tells git to remap notes to the new commit SHAs during rebase. No manual steps are needed.
When multiple developers use shiftlog on the same repository, conversation notes sync automatically via git push/pull hooks. Notes are merged seamlessly because each developer typically annotates different commits.

If you initialized before this config was added, run `shiftlog init` again or set it manually:
If notes diverge:

```bash
git config notes.rewriteRef refs/notes/shiftlog
shiftlog sync pull # Fetch and merge remote notes
shiftlog sync push # Now succeeds
```

You can verify the config is set with `shiftlog doctor`.

## GitHub Rebase Merge
## πŸ“Œ Git Operations

When a PR is merged via GitHub's "Rebase and merge" strategy, the commits get new SHAs on the target branch. Since this happens server-side, git's `notes.rewriteRef` does not apply and notes remain keyed to the original (now-orphaned) commit SHAs.
### Worktrees
Shiftlog is worktree-safe. Each worktree sees only conversations for commits on its own branch.

Shiftlog handles this automatically. After you pull a rebase-merged PR, the post-merge hook runs `shiftlog remap`, which:
### Rebase
Notes automatically follow commits during rebase via `notes.rewriteRef`. No manual steps needed.

1. Finds notes on commits that are no longer on any branch (orphaned)
2. Uses `git patch-id` to match each orphaned commit to its rebased counterpart by diff content
3. Copies the note to the new commit SHA (the original note is also kept)
### GitHub Rebase Merge
For PRs merged via "Rebase and merge", run `shiftlog remap` after pulling to remap orphaned notes to new commit SHAs.

For this to work, the local feature branch must be deleted or pruned so the old commits become orphaned. If your GitHub repo is configured to auto-delete branches after merge, this happens naturally when you `git pull` with `fetch.prune=true` (or `git fetch --prune`). Otherwise, delete the branch manually first:
## πŸ“Š Comparison

```bash
git branch -D feature-branch
shiftlog remap
```
| Feature | [Entire](https://entire.io) | Shift Log |
|---------|---------------------------|-----------|
| **Agents** | Claude Code, Gemini CLI | Claude, Codex, Copilot, Gemini, OpenCode |
| **Storage** | Custom checkpoints | Git Notes (standard) |
| **Resume Sessions** | ❌ | βœ… |
| **Web Viewer** | ❌ | βœ… |
| **Rebase Support** | ❌ | βœ… |
| **Open Source** | βœ… | βœ… |

You can also run `shiftlog remap` at any time β€” it reports how many notes were remapped and how many remain unmatched. Unmatched notes are left in place, not deleted.
## ❓ Why?

**Note:** Remap works with GitHub's "Rebase and merge" strategy. It does not support "Squash and merge", which combines all commits into one new commit with no 1:1 mapping to copy notes from.
To understand _how_ and _why_ a commit was made, you need to see the conversation that led to it. ShiftLog makes that possible.

## License
## πŸ“„ License

[AI Native Application License (AINAL)](https://github.com/re-cinq/ai-native-application-license) β€” see [LICENSE](LICENSE).

---

*README optimized with [Gingiris README Generator](https://gingiris.github.io/github-readme-generator/)*