git-stint 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -256
- package/adapters/claude-code/hooks/git-stint-hook-pre-tool +3 -1
- package/dist/cli.js +5 -0
- package/dist/install-hooks.js +2 -0
- package/dist/manifest.js +5 -0
- package/dist/session.d.ts +1 -0
- package/dist/session.js +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,87 +1,109 @@
|
|
|
1
1
|
# git-stint
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/git-stint)
|
|
4
|
+
[](https://github.com/rchaz/git-stint/actions)
|
|
3
5
|
[](LICENSE)
|
|
4
6
|
[](https://nodejs.org)
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
**Built for AI agents to multitask safely.**
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
Run multiple AI coding agents in parallel — each one gets its own branch, its own worktree, and its own lifecycle. git-stint handles the branching, tracking, checkpointing, and cleanup autonomously. You focus on the work. The grunt work is managed under the hood.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
No cloud VMs. No Docker containers. No desktop app. No new VCS to learn. Just git — with session management that runs itself.
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
```
|
|
15
|
+
Agent A fixes auth ──→ .stint/auth-fix/ → builds, tests, PRs independently
|
|
16
|
+
Agent B adds search ──→ .stint/add-search/ → builds, tests, PRs independently
|
|
17
|
+
Agent C writes docs ──→ .stint/update-docs/ → builds, tests, PRs independently
|
|
18
|
+
You keep working ──→ main branch, untouched
|
|
19
|
+
```
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
2. **Isolate sessions** — two parallel agents editing the same repo shouldn't conflict
|
|
16
|
-
3. **Produce clean commits** — agent work should result in reviewable, mergeable PRs
|
|
17
|
-
4. **Test in isolation** — verify one session's changes without interference
|
|
21
|
+
## The Problem
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
When an AI coding agent works on your repo, you lose track of what it changed, your main branch accumulates half-finished work, and running two agents at once creates conflicts. There's no clean way to:
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
- **Separate agent changes from human changes** — who wrote what?
|
|
26
|
+
- **Run agents in parallel** — two agents on the same branch will collide
|
|
27
|
+
- **Get clean, reviewable PRs** — agent work is often a stream of small edits
|
|
28
|
+
- **Undo an agent's work** — reverting scattered changes across files is painful
|
|
29
|
+
- **Prevent accidental damage** — agents write to `main` unless something stops them
|
|
22
30
|
|
|
23
|
-
-
|
|
24
|
-
- [git](https://git-scm.com) 2.20+ (worktree support)
|
|
25
|
-
- [`gh` CLI](https://cli.github.com) (optional, for PR creation)
|
|
31
|
+
## How git-stint Solves This
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
Each agent session gets a **real git branch** and a **worktree** — not virtual branches, not overlays, not a custom VCS. Standard git, fully compatible with every tool you already use.
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
Every step of the session lifecycle is autonomous:
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
| What happens | What git-stint does | You do anything? |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Agent writes its first file | Auto-creates a session branch + worktree | No |
|
|
40
|
+
| Agent keeps writing files | Hooks track every change automatically | No |
|
|
41
|
+
| Agent builds or runs tests | Runs in the isolated worktree — can't break main | No |
|
|
42
|
+
| Conversation ends unexpectedly | Auto-commits a WIP checkpoint — nothing lost | No |
|
|
43
|
+
| Work is ready to ship | `git stint squash` + `git stint pr` = clean PR | One command |
|
|
44
|
+
| Session is done | `git stint end` cleans up branch, worktree, and remote | One command |
|
|
32
45
|
|
|
33
|
-
|
|
46
|
+
## Key Features
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
1. Run `npm install -g git-stint`
|
|
37
|
-
2. Run `git stint install-hooks` (writes to `.claude/settings.json`)
|
|
38
|
-
3. Create a `.stint.json` with your preferred `main_branch_policy`
|
|
48
|
+
**Fully autonomous** — Install once, then forget about it. Hooks intercept file writes, create sessions, track changes, and checkpoint work without any manual intervention. The agent works on its stint. git-stint manages everything else.
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
**Parallel agents, zero conflicts** — Each agent instance gets its own session via process ID. Three Claude Code windows? Three isolated branches. Each can build, test, commit, and create PRs independently — no coordination required between them.
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
# 1. Install
|
|
44
|
-
npm install -g git-stint # from npm
|
|
45
|
-
# — or from source —
|
|
46
|
-
git clone https://github.com/rchaz/git-stint.git
|
|
47
|
-
cd git-stint && npm install && npm run build && npm link
|
|
52
|
+
**Safe builds and tests** — Every session runs in its own worktree. `npm test`, `cargo build`, `go test` — all execute against that session's files only. One agent's broken build can't block another. `git stint test --combine A B` lets you verify multiple sessions work together before merging any of them.
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
cd /path/to/your/repo
|
|
51
|
-
git stint install-hooks
|
|
54
|
+
**No work lost — ever** — Conversation timeout? Crash? User closes the window? The stop hook auto-commits pending changes as a WIP checkpoint. Come back later, the work is still there.
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
**Clean PRs from messy work** — An agent's stream of incremental edits becomes a single, reviewable commit with `git stint squash`. Create a PR with auto-generated descriptions in one command.
|
|
57
|
+
|
|
58
|
+
**Conflict detection before merge** — `git stint conflicts` checks file overlap across all active sessions. Know which agents are touching the same files *before* you try to merge.
|
|
59
|
+
|
|
60
|
+
**Shared directories** — Large caches (`node_modules`, `venv`, build outputs) are symlinked into worktrees instead of duplicated. Agents get fast startup, you save disk space.
|
|
61
|
+
|
|
62
|
+
**Zero dependencies** — Pure Node.js built-ins. No native modules, no runtime deps, no surprises. Installs in seconds.
|
|
63
|
+
|
|
64
|
+
**Just git underneath** — Real branches, real worktrees, real commits. `git log`, `git diff`, lazygit, VS Code, GitHub — every tool you already use works because there's nothing custom underneath.
|
|
61
65
|
|
|
62
66
|
## Quick Start
|
|
63
67
|
|
|
64
68
|
```bash
|
|
65
|
-
#
|
|
69
|
+
# Install
|
|
70
|
+
npm install -g git-stint
|
|
71
|
+
|
|
72
|
+
# Set up hooks in your repo
|
|
73
|
+
cd /path/to/your/repo
|
|
74
|
+
git stint install-hooks
|
|
75
|
+
|
|
76
|
+
# Start a session
|
|
66
77
|
git stint start auth-fix
|
|
67
78
|
cd .stint/auth-fix/
|
|
68
79
|
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
# Commit progress (advances baseline)
|
|
80
|
+
# Make changes, then commit
|
|
72
81
|
git stint commit -m "Fix token refresh logic"
|
|
73
82
|
|
|
74
|
-
#
|
|
75
|
-
git stint commit -m "Add refresh token tests"
|
|
76
|
-
|
|
77
|
-
# Squash into a single clean commit
|
|
83
|
+
# Squash into a clean commit and create a PR
|
|
78
84
|
git stint squash -m "Fix auth token refresh"
|
|
79
|
-
|
|
80
|
-
# Create PR and clean up
|
|
81
85
|
git stint pr --title "Fix auth bug"
|
|
86
|
+
|
|
87
|
+
# Clean up
|
|
82
88
|
git stint end
|
|
83
89
|
```
|
|
84
90
|
|
|
91
|
+
### With Claude Code
|
|
92
|
+
|
|
93
|
+
Tell Claude Code:
|
|
94
|
+
|
|
95
|
+
> Install git-stint globally (`npm install -g git-stint`), set up hooks for this repo, and create a .stint.json
|
|
96
|
+
|
|
97
|
+
Or manually:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install -g git-stint
|
|
101
|
+
cd /path/to/your/repo
|
|
102
|
+
git stint install-hooks # Writes to .claude/settings.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Once hooks are installed, Claude Code automatically works in sessions. When `main_branch_policy` is `"block"`, the first file write auto-creates a session and redirects the agent — zero manual steps.
|
|
106
|
+
|
|
85
107
|
## Commands
|
|
86
108
|
|
|
87
109
|
| Command | Description |
|
|
@@ -89,45 +111,44 @@ git stint end
|
|
|
89
111
|
| `git stint start [name]` | Create a new session (branch + worktree) |
|
|
90
112
|
| `git stint list` | List all active sessions |
|
|
91
113
|
| `git stint status` | Show current session state |
|
|
92
|
-
| `git stint track <file...>` | Add files to the pending list |
|
|
93
114
|
| `git stint diff` | Show uncommitted changes in worktree |
|
|
94
115
|
| `git stint commit -m "msg"` | Commit changes, advance baseline |
|
|
95
116
|
| `git stint log` | Show session commit history |
|
|
96
117
|
| `git stint squash -m "msg"` | Collapse all commits into one |
|
|
97
|
-
| `git stint merge` | Merge session into current branch
|
|
118
|
+
| `git stint merge` | Merge session into current branch |
|
|
98
119
|
| `git stint pr [--title "..."]` | Push branch and create GitHub PR |
|
|
99
|
-
| `git stint end` | Finalize session, clean up everything
|
|
120
|
+
| `git stint end` | Finalize session, clean up everything |
|
|
100
121
|
| `git stint abort` | Discard session — delete all changes |
|
|
101
122
|
| `git stint undo` | Revert last commit, changes become pending |
|
|
123
|
+
| `git stint which [--worktree]` | Print resolved session name (or worktree path) |
|
|
102
124
|
| `git stint conflicts` | Check file overlap with other sessions |
|
|
103
125
|
| `git stint test [-- cmd]` | Run tests in the session worktree |
|
|
104
126
|
| `git stint test --combine A B` | Test multiple sessions merged together |
|
|
127
|
+
| `git stint track <file...>` | Add files to the pending list |
|
|
105
128
|
| `git stint prune` | Clean up orphaned worktrees/branches |
|
|
106
|
-
| `git stint allow-main
|
|
129
|
+
| `git stint allow-main` | Allow writes to main (scoped to one process) |
|
|
107
130
|
| `git stint install-hooks` | Install Claude Code hooks |
|
|
108
131
|
| `git stint uninstall-hooks` | Remove Claude Code hooks |
|
|
109
132
|
|
|
110
133
|
### Options
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
| Flag | Description |
|
|
136
|
+
|------|-------------|
|
|
137
|
+
| `--session <name>` | Target a specific session (auto-detected from CWD) |
|
|
138
|
+
| `--client-id <id>` | Client identifier for multi-instance affinity |
|
|
139
|
+
| `--adopt` / `--no-adopt` | Control whether uncommitted changes carry into a new session |
|
|
140
|
+
| `-m "message"` | Commit or squash message |
|
|
141
|
+
| `--title "title"` | PR title |
|
|
142
|
+
| `--version` | Show version number |
|
|
118
143
|
|
|
119
144
|
## Configuration — `.stint.json`
|
|
120
145
|
|
|
121
|
-
Create a `.stint.json`
|
|
146
|
+
Create a `.stint.json` in your repo root:
|
|
122
147
|
|
|
123
148
|
```json
|
|
124
149
|
{
|
|
125
|
-
"shared_dirs": [
|
|
126
|
-
|
|
127
|
-
"backend/results",
|
|
128
|
-
"backend/logs"
|
|
129
|
-
],
|
|
130
|
-
"main_branch_policy": "prompt",
|
|
150
|
+
"shared_dirs": ["node_modules", ".venv", "dist"],
|
|
151
|
+
"main_branch_policy": "block",
|
|
131
152
|
"force_cleanup": "prompt",
|
|
132
153
|
"adopt_changes": "always"
|
|
133
154
|
}
|
|
@@ -135,234 +156,105 @@ Create a `.stint.json` file in your repo root to configure git-stint behavior:
|
|
|
135
156
|
|
|
136
157
|
| Field | Values | Default | Description |
|
|
137
158
|
|-------|--------|---------|-------------|
|
|
138
|
-
| `shared_dirs` | `string[]` | `[]` | Directories to symlink from worktree to main repo
|
|
139
|
-
| `main_branch_policy` | `"
|
|
140
|
-
| `force_cleanup` | `"
|
|
141
|
-
| `adopt_changes` | `"always"` / `"never"` / `"prompt"` | `"always"` |
|
|
142
|
-
|
|
143
|
-
### Shared Directories
|
|
144
|
-
|
|
145
|
-
When a worktree is created, gitignored directories (caches, build outputs, data) don't exist in it. Without `shared_dirs`, you'd need to manually symlink or recreate them.
|
|
146
|
-
|
|
147
|
-
With `shared_dirs` configured, `git stint start` automatically:
|
|
148
|
-
1. Creates symlinks from the worktree to the main repo for each listed directory
|
|
149
|
-
2. On `git stint end` / `abort`, removes the symlinks before deleting the worktree — so linked data is never lost
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
# Main repo # Worktree (.stint/my-session/)
|
|
153
|
-
backend/data/ (200MB cache) ←── backend/data → symlink to main
|
|
154
|
-
backend/results/ ←── backend/results → symlink to main
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
The directories listed in `shared_dirs` should typically be gitignored, since they contain large or generated data that shouldn't be committed.
|
|
159
|
+
| `shared_dirs` | `string[]` | `[]` | Directories to symlink from worktree to main repo. Use for gitignored dirs (caches, build outputs) that shouldn't be duplicated per session. |
|
|
160
|
+
| `main_branch_policy` | `"block"` / `"prompt"` / `"allow"` | `"prompt"` | What happens when an agent writes to main. `"block"` auto-creates a session. `"prompt"` blocks with instructions. `"allow"` passes through. |
|
|
161
|
+
| `force_cleanup` | `"force"` / `"prompt"` / `"fail"` | `"prompt"` | Behavior when worktree removal fails. |
|
|
162
|
+
| `adopt_changes` | `"always"` / `"never"` / `"prompt"` | `"always"` | Whether uncommitted changes on main carry into new sessions. |
|
|
158
163
|
|
|
159
164
|
### Main Branch Policy
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
The hook intercepts every file write from the AI agent:
|
|
162
167
|
|
|
163
|
-
- **`"block"`** — Auto-creates a session
|
|
164
|
-
- **`"prompt"`** (default) — Blocks with a message
|
|
165
|
-
- **`"allow"`** — Passes through
|
|
168
|
+
- **`"block"`** — Auto-creates a session on the first write. The agent is seamlessly redirected to the worktree. Most protective mode.
|
|
169
|
+
- **`"prompt"`** (default) — Blocks with a message showing the exact command to unblock (`git stint allow-main --client-id <PID>`). Lets you decide per-situation.
|
|
170
|
+
- **`"allow"`** — Passes through. Hooks still track files in existing sessions but don't enforce session usage.
|
|
166
171
|
|
|
167
|
-
The `allow-main` flag is scoped per-process. When the hook blocks a write, it prints the exact command with the correct `--client-id
|
|
168
|
-
|
|
169
|
-
**Important:** Always use the `--client-id` value from the hook's block message. Running `allow-main` without `--client-id` from a separate terminal will NOT unblock Claude Code (different process tree). The intended flow is:
|
|
170
|
-
|
|
171
|
-
1. Hook blocks Claude's write and prints: `git stint allow-main --client-id <PID>`
|
|
172
|
-
2. Claude (or the user telling Claude) runs that exact command
|
|
173
|
-
3. Claude retries the write — it succeeds
|
|
174
|
-
|
|
175
|
-
Stale flags from dead processes are cleaned up by `git stint prune`.
|
|
172
|
+
The `allow-main` flag is scoped per-process. When the hook blocks a write, it prints the exact command with the correct `--client-id`. Only that specific agent instance is unblocked — other instances stay protected. Stale flags from dead processes are cleaned by `git stint prune`.
|
|
176
173
|
|
|
177
174
|
### Adopting Uncommitted Changes
|
|
178
175
|
|
|
179
|
-
When you run `git stint start` with uncommitted changes on main
|
|
176
|
+
When you run `git stint start` with uncommitted changes on main:
|
|
180
177
|
|
|
181
|
-
- **`"always"`** (default) — Stashes changes
|
|
182
|
-
- **`"never"`** — Leaves uncommitted changes on main.
|
|
183
|
-
- **`"prompt"`** — Warns
|
|
184
|
-
|
|
185
|
-
CLI flags override the config for a single invocation:
|
|
178
|
+
- **`"always"`** (default) — Stashes changes, pops them into the new worktree. Your work carries over seamlessly.
|
|
179
|
+
- **`"never"`** — Leaves uncommitted changes on main. New worktree starts clean.
|
|
180
|
+
- **`"prompt"`** — Warns and suggests `--adopt` or `--no-adopt`.
|
|
186
181
|
|
|
187
182
|
```bash
|
|
188
|
-
git stint start my-feature --adopt # Force adopt
|
|
189
|
-
git stint start my-feature --no-adopt #
|
|
183
|
+
git stint start my-feature --adopt # Force adopt regardless of config
|
|
184
|
+
git stint start my-feature --no-adopt # Skip regardless of config
|
|
190
185
|
```
|
|
191
186
|
|
|
192
|
-
##
|
|
193
|
-
|
|
194
|
-
git-stint includes hooks that make it work seamlessly with [Claude Code](https://docs.anthropic.com/en/docs/claude-code):
|
|
195
|
-
|
|
196
|
-
- **PreToolUse hook**: When Claude writes/edits a file inside a session worktree, the file is automatically tracked. If Claude tries to write to the main repo, behavior depends on `main_branch_policy` in `.stint.json`. Writes to gitignored files (e.g. `node_modules/`, `dist/`, `.env`) are always allowed through — they can't be committed, so they don't need branch isolation.
|
|
197
|
-
- **Stop hook**: When a Claude Code conversation ends, pending changes are auto-committed as a WIP checkpoint.
|
|
198
|
-
- **Session affinity**: Each Claude Code instance is mapped to its own session via `clientId` (process ID). Multiple Claude instances can work in parallel without hijacking each other's sessions.
|
|
199
|
-
|
|
200
|
-
### Setup for Claude Code
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
# 1. Install git-stint globally
|
|
204
|
-
npm install -g git-stint
|
|
205
|
-
|
|
206
|
-
# 2. Navigate to your project
|
|
207
|
-
cd /path/to/your/repo
|
|
187
|
+
## How It Works
|
|
208
188
|
|
|
209
|
-
|
|
210
|
-
git stint install-hooks
|
|
189
|
+
### Session Model
|
|
211
190
|
|
|
212
|
-
|
|
213
|
-
cat > .stint.json << 'EOF'
|
|
214
|
-
{
|
|
215
|
-
"shared_dirs": [],
|
|
216
|
-
"main_branch_policy": "prompt"
|
|
217
|
-
}
|
|
218
|
-
EOF
|
|
191
|
+
Each session creates three things:
|
|
219
192
|
|
|
220
|
-
# 5. Done — Claude Code will now auto-track files in sessions
|
|
221
193
|
```
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
git stint install-hooks --user
|
|
194
|
+
Branch: stint/auth-fix (real git branch, forked from HEAD)
|
|
195
|
+
Worktree: .stint/auth-fix/ (isolated working directory)
|
|
196
|
+
Manifest: .git/sessions/auth-fix.json (session state, disposable)
|
|
227
197
|
```
|
|
228
198
|
|
|
229
|
-
|
|
199
|
+
The **baseline cursor** advances on each commit. `git diff baseline..HEAD` always shows exactly the uncommitted work — no guesswork about what changed.
|
|
230
200
|
|
|
231
|
-
|
|
201
|
+
### Session Resolution
|
|
232
202
|
|
|
233
|
-
|
|
234
|
-
# Start a session before asking Claude to work
|
|
235
|
-
git stint start my-feature
|
|
236
|
-
cd .stint/my-feature/
|
|
203
|
+
You rarely need `--session`. git-stint resolves the active session automatically:
|
|
237
204
|
|
|
238
|
-
|
|
205
|
+
1. Explicit `--session <name>` flag
|
|
206
|
+
2. CWD inside a `.stint/<name>/` worktree
|
|
207
|
+
3. Process-based affinity (agent's PID maps to its session)
|
|
208
|
+
4. Single session fallback (if only one session exists)
|
|
239
209
|
|
|
240
|
-
|
|
241
|
-
git stint squash -m "Implement feature X"
|
|
242
|
-
git stint pr
|
|
243
|
-
git stint end
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
Or let the hooks auto-create sessions — just start coding with Claude and the hook will create a session on the first write (when `main_branch_policy` is `"block"`).
|
|
247
|
-
|
|
248
|
-
**Option B: Quick edits on main (allow-main)**
|
|
210
|
+
### Parallel Sessions
|
|
249
211
|
|
|
250
|
-
|
|
212
|
+
Each session is fully isolated — separate branch, separate worktree, separate filesystem. Agents can safely do everything in parallel:
|
|
251
213
|
|
|
252
214
|
```
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
215
|
+
Session A: .stint/auth-fix/ ──→ edit, build, test, commit, PR
|
|
216
|
+
Session B: .stint/add-tests/ ──→ edit, build, test, commit, PR
|
|
217
|
+
Session C: .stint/refactor/ ──→ edit, build, test, commit, PR
|
|
218
|
+
└── all running simultaneously
|
|
256
219
|
```
|
|
257
220
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
## How It Works
|
|
261
|
-
|
|
262
|
-
### Session Model
|
|
221
|
+
One agent's `npm install` doesn't interfere with another's build. One agent's failing test doesn't block another's PR. Overlapping file edits are caught early by `git stint conflicts`, and final integration uses git's standard merge machinery.
|
|
263
222
|
|
|
264
|
-
|
|
265
|
-
- A **git branch** (`stint/<name>`) forked from HEAD
|
|
266
|
-
- A **worktree** (`.stint/<name>/`) for isolated file access
|
|
267
|
-
- A **manifest** (`.git/sessions/<name>.json`) tracking state
|
|
223
|
+
### Claude Code Hooks
|
|
268
224
|
|
|
269
|
-
|
|
270
|
-
Session starts at HEAD = abc123
|
|
271
|
-
|
|
|
272
|
-
Edit config.ts, server.ts
|
|
273
|
-
|
|
|
274
|
-
"commit" -> changeset 1 (baseline advances to new SHA)
|
|
275
|
-
|
|
|
276
|
-
Edit server.ts (again), test.ts
|
|
277
|
-
|
|
|
278
|
-
"commit" -> changeset 2 (only NEW changes since last commit)
|
|
279
|
-
```
|
|
225
|
+
Two hooks make git-stint work automatically with Claude Code:
|
|
280
226
|
|
|
281
|
-
|
|
227
|
+
**PreToolUse** — Intercepts every `Write`, `Edit`, and `NotebookEdit` tool call. If the file is inside a session worktree, it's tracked. If it's on main, the hook enforces the configured policy (block, prompt, or allow). Writes to gitignored files always pass through.
|
|
282
228
|
|
|
283
|
-
|
|
229
|
+
**Stop** — When a conversation ends, commits all pending changes as a WIP checkpoint. No work is ever lost to timeouts or closed windows.
|
|
284
230
|
|
|
285
|
-
|
|
231
|
+
To install hooks globally (all repos):
|
|
286
232
|
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
Session B: edits server.ts, constants.ts -> .stint/session-b/
|
|
290
|
-
^ overlap detected by `git stint conflicts`
|
|
233
|
+
```bash
|
|
234
|
+
git stint install-hooks --user
|
|
291
235
|
```
|
|
292
236
|
|
|
293
|
-
|
|
237
|
+
### Smart Cleanup
|
|
294
238
|
|
|
295
|
-
|
|
239
|
+
`git stint end` handles everything:
|
|
296
240
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# Test multiple sessions merged together
|
|
302
|
-
git stint test --combine auth-fix perf-update -- npm test
|
|
303
|
-
```
|
|
241
|
+
- Removes symlinks before deleting worktrees (linked data is never lost)
|
|
242
|
+
- Deletes the remote branch **only** when changes are verified merged on the remote — checks against `origin/main`, not local branches
|
|
243
|
+
- Two-tier merge verification: commit ancestry for regular merges, content diff for squash/rebase merges
|
|
244
|
+
- Network errors never block cleanup — unmerged branches are preserved with a warning
|
|
304
245
|
|
|
305
|
-
|
|
246
|
+
### Safety
|
|
306
247
|
|
|
307
|
-
|
|
248
|
+
- All git commands use `execFileSync` with array arguments — no shell injection
|
|
249
|
+
- Session names are validated (alphanumeric, hyphens, dots, underscores — no path traversal)
|
|
250
|
+
- Manifest writes are atomic (temp file + rename) — crash-safe
|
|
251
|
+
- `.stint/` is excluded via `.git/info/exclude` — never pollutes `.gitignore`
|
|
308
252
|
|
|
309
|
-
|
|
310
|
-
+----------------+
|
|
311
|
-
| .stint.json |
|
|
312
|
-
| (config) |
|
|
313
|
-
+-------+--------+
|
|
314
|
-
|
|
|
315
|
-
+-----------+ +-----------+ v +----------------+
|
|
316
|
-
| CLI | | Session |------->| Config |
|
|
317
|
-
| (cli.ts) |--->|(session.ts)| | (config.ts) |
|
|
318
|
-
| arg parse | | commands |---+ +----------------+
|
|
319
|
-
+-----------+ +-----+-----+ |
|
|
320
|
-
| +--->+----------------+
|
|
321
|
-
+------v------+ | Manifest |
|
|
322
|
-
| Git | | (manifest.ts) |
|
|
323
|
-
| (git.ts) | | JSON state |
|
|
324
|
-
| plumbing | +----------------+
|
|
325
|
-
+-------------+
|
|
326
|
-
```
|
|
253
|
+
## Prerequisites
|
|
327
254
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
| `src/manifest.ts` | Session state CRUD in `.git/sessions/` | ~200 |
|
|
332
|
-
| `src/session.ts` | Core commands (start, commit, squash, pr, end...) | ~830 |
|
|
333
|
-
| `src/config.ts` | `.stint.json` loading and validation | ~60 |
|
|
334
|
-
| `src/conflicts.ts` | Cross-session file overlap detection | ~55 |
|
|
335
|
-
| `src/test-session.ts` | Worktree-based testing + combined testing | ~140 |
|
|
336
|
-
| `src/cli.ts` | Entry point, argument parsing | ~300 |
|
|
337
|
-
| `src/install-hooks.ts` | Claude Code hook installation/removal | ~170 |
|
|
338
|
-
| `adapters/claude-code/hooks/` | Bash hooks (PreToolUse + Stop) | ~220 |
|
|
339
|
-
|
|
340
|
-
### Design Decisions
|
|
341
|
-
|
|
342
|
-
- **Real git branches** — not virtual branches. Every git tool works: `git log`, `git diff`, lazygit, tig, VS Code.
|
|
343
|
-
- **Worktrees for isolation** — the default state is isolated. No unapply/apply dance.
|
|
344
|
-
- **JSON manifests** — stored in `.git/sessions/`. Disposable. Worst case: delete and start over.
|
|
345
|
-
- **No custom merge engine** — git's built-in merge handles everything. Source of most GitButler complexity eliminated.
|
|
346
|
-
- **`execFileSync` everywhere** — array arguments prevent shell injection. No `execSync` with string interpolation.
|
|
347
|
-
- **Atomic manifest writes** — write to `.tmp`, then `rename()`. Crash-safe.
|
|
348
|
-
- **Symlinks for shared data** — gitignored dirs (caches, data) symlink into worktrees instead of being copied or lost.
|
|
349
|
-
- **Safe remote branch cleanup** — on `end`/`merge`, deletes the remote branch only when all changes are verified merged **on the remote**. Checks against remote tracking refs (`origin/main`), not local branches — so a local-only merge won't delete the remote branch until you push. Uses a two-tier check (commit ancestry + content diff) that handles regular merges, squash merges, and rebase merges. Unmerged branches are always preserved with a warning.
|
|
350
|
-
- **Zero runtime dependencies** — only Node.js built-ins. Dev deps are TypeScript and @types/node.
|
|
351
|
-
|
|
352
|
-
## git-stint vs GitButler
|
|
353
|
-
|
|
354
|
-
| Aspect | git-stint | GitButler |
|
|
355
|
-
|--------|-----------|-----------|
|
|
356
|
-
| Isolation | Default (each branch IS isolated) | Opt-in (unapply other branches) |
|
|
357
|
-
| Branch storage | Real git branches | Virtual branches (TOML + SQLite) |
|
|
358
|
-
| Working dir | One branch per worktree | Permanent octopus merge overlay |
|
|
359
|
-
| Merge engine | Git's built-in | Custom hunk-level engine |
|
|
360
|
-
| Git compatibility | Full — all git tools work | Partial — writes break state |
|
|
361
|
-
| State | JSON manifests (disposable) | SQLite + TOML (can corrupt) |
|
|
362
|
-
| Code size | ~2,000 lines TypeScript + bash | ~100k+ lines Rust |
|
|
363
|
-
| Dependencies | git, gh (optional) | Tauri desktop app |
|
|
364
|
-
|
|
365
|
-
git-stint is designed for AI agent workflows where sessions are independent and short-lived. GitButler is a full-featured branch management GUI for teams.
|
|
255
|
+
- [Node.js](https://nodejs.org) 20+
|
|
256
|
+
- [git](https://git-scm.com) 2.20+ (worktree support)
|
|
257
|
+
- [`gh` CLI](https://cli.github.com) (optional, for `git stint pr`)
|
|
366
258
|
|
|
367
259
|
## Development
|
|
368
260
|
|
|
@@ -375,16 +267,14 @@ npm link # Install globally for testing
|
|
|
375
267
|
|
|
376
268
|
# Run tests
|
|
377
269
|
npm test # Unit tests
|
|
378
|
-
npm run test:
|
|
379
|
-
npm run test:integration # Integration tests
|
|
380
|
-
npm run test:all # Everything (build + all tests)
|
|
270
|
+
npm run test:all # Everything (build + all tests)
|
|
381
271
|
```
|
|
382
272
|
|
|
383
273
|
## Contributing
|
|
384
274
|
|
|
385
275
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and PR guidelines.
|
|
386
276
|
|
|
387
|
-
|
|
277
|
+
This project uses a [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
388
278
|
|
|
389
279
|
## License
|
|
390
280
|
|
|
@@ -164,7 +164,9 @@ if [ -n "$CLIENT_ID" ]; then
|
|
|
164
164
|
CLIENT_FLAG="--client-id $CLIENT_ID"
|
|
165
165
|
fi
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
# Always adopt uncommitted changes when auto-creating — files may have been
|
|
168
|
+
# written to main via Bash (bypassing hooks) before this session was created.
|
|
169
|
+
START_OUTPUT=$(git-stint start "$SESSION_NAME" --adopt $CLIENT_FLAG 2>&1) || {
|
|
168
170
|
# If start fails, allow the write
|
|
169
171
|
exit 0
|
|
170
172
|
}
|
package/dist/cli.js
CHANGED
|
@@ -166,6 +166,10 @@ try {
|
|
|
166
166
|
}
|
|
167
167
|
break;
|
|
168
168
|
}
|
|
169
|
+
case "which": {
|
|
170
|
+
session.which(getFlag("--session"), args.includes("--worktree"));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
169
173
|
case "list": {
|
|
170
174
|
if (args.includes("--json")) {
|
|
171
175
|
session.listJson();
|
|
@@ -253,6 +257,7 @@ Commands:
|
|
|
253
257
|
end Finalize session, clean up everything
|
|
254
258
|
abort Discard session — delete all changes
|
|
255
259
|
undo Revert last commit, changes become pending
|
|
260
|
+
which [--worktree] Print resolved session name or worktree path
|
|
256
261
|
conflicts Check file overlap with other sessions
|
|
257
262
|
test [-- <cmd>] Run tests in the session worktree
|
|
258
263
|
test --combine A B Test multiple sessions merged together
|
package/dist/install-hooks.js
CHANGED
|
@@ -104,6 +104,8 @@ The name becomes the branch (\`stint/<name>\`) and the PR title context.
|
|
|
104
104
|
## Session Lifecycle
|
|
105
105
|
|
|
106
106
|
- If the hook blocks a write, create a session: \`git stint start <descriptive-name>\`
|
|
107
|
+
- Any uncommitted files on main are automatically carried into the new session.
|
|
108
|
+
Do NOT redo work that was already written — it is adopted into the worktree.
|
|
107
109
|
- All edits redirect to \`.stint/<session>/\` worktree.
|
|
108
110
|
- \`git stint commit -m "msg"\` to commit logical units of work.
|
|
109
111
|
- \`git stint pr\` to push and create PR.
|
package/dist/manifest.js
CHANGED
|
@@ -128,6 +128,11 @@ export function resolveSession(explicit) {
|
|
|
128
128
|
return manifests[0];
|
|
129
129
|
if (manifests.length === 0)
|
|
130
130
|
throw new Error("No active sessions. Run `git stint start <name>` to create one.");
|
|
131
|
+
// PPID-based clientId affinity
|
|
132
|
+
const ppid = String(process.ppid);
|
|
133
|
+
const clientMatch = manifests.filter(m => m.clientId === ppid);
|
|
134
|
+
if (clientMatch.length === 1)
|
|
135
|
+
return clientMatch[0];
|
|
131
136
|
const names = manifests.map((m) => m.name).join(", ");
|
|
132
137
|
throw new Error(`Multiple active sessions: ${names}.\n` +
|
|
133
138
|
`Use --session <name> to specify, or cd into a worktree.\n` +
|
package/dist/session.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare function end(sessionName?: string): void;
|
|
|
13
13
|
export declare function abort(sessionName?: string): void;
|
|
14
14
|
/** Revert last commit, keeping changes as unstaged files. */
|
|
15
15
|
export declare function undo(sessionName?: string): void;
|
|
16
|
+
export declare function which(sessionName?: string, showWorktree?: boolean): void;
|
|
16
17
|
export declare function list(): void;
|
|
17
18
|
export declare function listJson(): void;
|
|
18
19
|
/** Clean up orphaned worktrees, manifests, and branches. */
|
package/dist/session.js
CHANGED
|
@@ -536,6 +536,15 @@ export function undo(sessionName) {
|
|
|
536
536
|
console.log(`Undid commit: ${last.sha.slice(0, 8)} ${last.message}`);
|
|
537
537
|
console.log(`${last.files.length} file(s) back to pending.`);
|
|
538
538
|
}
|
|
539
|
+
export function which(sessionName, showWorktree) {
|
|
540
|
+
const manifest = resolveSession(sessionName);
|
|
541
|
+
if (showWorktree) {
|
|
542
|
+
console.log(getWorktreePath(manifest));
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
console.log(manifest.name);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
539
548
|
export function list() {
|
|
540
549
|
const manifests = listManifests();
|
|
541
550
|
if (manifests.length === 0) {
|