opencode-gitbutler 0.1.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.
@@ -0,0 +1,21 @@
1
+ export type LogLevel = "info" | "warn" | "error";
2
+ export type Logger = {
3
+ info: (cat: string, data?: Record<string, unknown>) => void;
4
+ warn: (cat: string, data?: Record<string, unknown>) => void;
5
+ error: (cat: string, data?: Record<string, unknown>) => void;
6
+ };
7
+ /**
8
+ * Structured NDJSON logger with explicit levels.
9
+ *
10
+ * Each line is a self-contained JSON object:
11
+ * {"ts":"...","level":"info","cat":"cursor-ok","subcommand":"after-edit",...}
12
+ *
13
+ * Reserved keys: ts, level, cat. All data fields are spread at top level.
14
+ * Parseable with: jq, grep, or any NDJSON-aware tool.
15
+ * Filter examples:
16
+ * jq 'select(.level == "error")'
17
+ * jq 'select(.cat == "cursor-ok")'
18
+ * grep '"cat":"llm-' debug.log | jq .
19
+ */
20
+ export declare function createLogger(logEnabled: boolean, cwd: string): Logger;
21
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC5D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAwBrE"}
@@ -0,0 +1,11 @@
1
+ import type { Logger } from "./logger.js";
2
+ export type ContextNotification = {
3
+ message: string;
4
+ timestamp: number;
5
+ };
6
+ export type NotificationManager = {
7
+ addNotification: (sessionID: string | undefined, message: string) => void;
8
+ consumeNotifications: (sessionID: string) => string | null;
9
+ };
10
+ export declare function createNotificationManager(log: Logger, resolveSessionRoot: (sessionID: string | undefined) => string, maxAgeMs?: number): NotificationManager;
11
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,oBAAoB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC5D,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,EAC7D,QAAQ,SAAU,GACjB,mBAAmB,CAuFrB"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * OpenCode plugin: GitButler integration via Cursor hook facade.
3
+ *
4
+ * Bridges OpenCode's plugin hooks to GitButler's `but cursor` CLI:
5
+ * - tool.execute.after (edit/write) -> but cursor after-edit
6
+ * - session.idle -> but cursor stop
7
+ * - experimental.chat.messages.transform -> inject pending state notifications
8
+ *
9
+ * This enables automatic branch creation, file-to-branch assignment,
10
+ * and auto-commit when using GitButler workspace mode with OpenCode.
11
+ *
12
+ * Uses Cursor hook format because it has simpler stdin JSON requirements
13
+ * than Claude Code hooks (no transcript_path needed).
14
+ *
15
+ * Multi-agent support: Each OpenCode session gets its own branch via
16
+ * conversation_id isolation in GitButler's session tracking.
17
+ */
18
+ import type { Plugin } from "@opencode-ai/plugin";
19
+ import type { GitButlerPluginConfig } from "./config.js";
20
+ export declare function createGitButlerPlugin(config?: GitButlerPluginConfig): Plugin;
21
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAUzD,wBAAgB,qBAAqB,CACnC,MAAM,GAAE,qBAA6C,GACpD,MAAM,CA+nBR"}
@@ -0,0 +1,104 @@
1
+ import type { Logger } from "./logger.js";
2
+ import type { Cli } from "./cli.js";
3
+ import type { BranchOwnership } from "./state.js";
4
+ import type { NotificationManager } from "./notify.js";
5
+ import type { GitButlerPluginConfig } from "./config.js";
6
+ export type RewordDeps = {
7
+ cwd: string;
8
+ log: Logger;
9
+ cli: Cli;
10
+ config: GitButlerPluginConfig;
11
+ defaultBranchPattern: RegExp;
12
+ addNotification: NotificationManager["addNotification"];
13
+ resolveSessionRoot: (sessionID: string | undefined) => string;
14
+ conversationsWithEdits: Set<string>;
15
+ rewordedBranches: Set<string>;
16
+ branchOwnership: Map<string, BranchOwnership>;
17
+ editedFilesPerConversation: Map<string, Set<string>>;
18
+ savePluginState: (conversations: Set<string>, reworded: Set<string>, ownership: Map<string, BranchOwnership>) => Promise<void>;
19
+ internalSessionIds: Set<string>;
20
+ reapStaleLocks: () => void;
21
+ client: {
22
+ session: {
23
+ messages: (opts: {
24
+ path: {
25
+ id: string;
26
+ };
27
+ query: {
28
+ limit: number;
29
+ };
30
+ }) => Promise<{
31
+ data?: Array<{
32
+ info: {
33
+ role: string;
34
+ };
35
+ parts: Array<{
36
+ type: string;
37
+ text?: string;
38
+ }>;
39
+ }>;
40
+ }>;
41
+ create: (opts: {
42
+ body: {
43
+ title: string;
44
+ };
45
+ }) => Promise<{
46
+ data?: {
47
+ id: string;
48
+ };
49
+ }>;
50
+ prompt: (opts: {
51
+ path: {
52
+ id: string;
53
+ };
54
+ body: {
55
+ model: {
56
+ providerID: string;
57
+ modelID: string;
58
+ };
59
+ system: string;
60
+ tools: Record<string, never>;
61
+ parts: Array<{
62
+ type: "text";
63
+ text: string;
64
+ }>;
65
+ };
66
+ }) => Promise<{
67
+ data?: {
68
+ parts: Array<{
69
+ type: string;
70
+ text?: string;
71
+ }>;
72
+ };
73
+ }>;
74
+ delete: (opts: {
75
+ path: {
76
+ id: string;
77
+ };
78
+ }) => Promise<unknown>;
79
+ update: (opts: {
80
+ path: {
81
+ id: string;
82
+ };
83
+ body: {
84
+ title: string;
85
+ };
86
+ }) => Promise<unknown>;
87
+ };
88
+ };
89
+ };
90
+ export declare const COMMIT_PREFIX_PATTERNS: Array<{
91
+ pattern: RegExp;
92
+ prefix: string;
93
+ }>;
94
+ export declare function detectCommitPrefix(text: string): string;
95
+ export declare function toCommitMessage(prompt: string): string;
96
+ export declare function toBranchSlug(prompt: string, maxLength: number): string;
97
+ export type RewordManager = {
98
+ fetchUserPrompt: (sessionID: string) => Promise<string | null>;
99
+ generateLLMCommitMessage: (commitId: string, userPrompt: string) => Promise<string | null>;
100
+ postStopProcessing: (sessionID: string | undefined, conversationId: string, stopFailed?: boolean) => Promise<void>;
101
+ };
102
+ export declare function classifyRewordFailure(stderr: string): string;
103
+ export declare function createRewordManager(deps: RewordDeps): RewordManager;
104
+ //# sourceMappingURL=reword.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reword.d.ts","sourceRoot":"","sources":["../src/reword.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { Logger } from "./logger.js";
2
+ export type HookInput = {
3
+ tool?: string;
4
+ sessionID?: string;
5
+ callID?: string;
6
+ };
7
+ export type HookOutput = {
8
+ title?: string;
9
+ output?: string;
10
+ metadata?: Record<string, unknown>;
11
+ };
12
+ export type EventPayload = Record<string, unknown> & {
13
+ type?: string;
14
+ properties?: Record<string, unknown>;
15
+ };
16
+ export type BranchOwnership = {
17
+ rootSessionID: string;
18
+ branchName: string;
19
+ firstSeen: number;
20
+ };
21
+ export type PluginState = {
22
+ conversationsWithEdits: string[];
23
+ rewordedBranches: string[];
24
+ branchOwnership: Record<string, BranchOwnership>;
25
+ };
26
+ export declare const SUBAGENT_TOOLS: Set<string>;
27
+ export type StateManager = {
28
+ loadPluginState: () => Promise<PluginState>;
29
+ savePluginState: (conversations: Set<string>, reworded: Set<string>, ownership: Map<string, BranchOwnership>) => Promise<void>;
30
+ loadSessionMap: () => Promise<Map<string, string>>;
31
+ saveSessionMap: (map: Map<string, string>) => Promise<void>;
32
+ resolveSessionRoot: (sessionID: string | undefined) => string;
33
+ trackSubagentMapping: (input: HookInput, output?: HookOutput) => Promise<void>;
34
+ trackSessionCreatedMapping: (event: EventPayload) => Promise<void>;
35
+ parentSessionByTaskSession: Map<string, string>;
36
+ };
37
+ export declare function createStateManager(cwd: string, log: Logger): StateManager;
38
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,cAAc,aAIzB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5C,eAAe,EAAE,CACf,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,EAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,KACpC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,cAAc,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,CAAC;IAC9D,oBAAoB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,0BAA0B,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,0BAA0B,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,YAAY,CA6Md"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "opencode-gitbutler",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "OpenCode plugin for GitButler integration",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "bun build src/index.ts --outdir dist --target bun --format esm && tsc --emitDeclarationOnly",
16
+ "clean": "rm -rf dist",
17
+ "typecheck": "tsc --noEmit",
18
+ "prepublishOnly": "bun run clean && bun run build",
19
+ "postinstall": "node postinstall.mjs",
20
+ "test": "bun test"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "postinstall.mjs",
25
+ "skill",
26
+ "command"
27
+ ],
28
+ "keywords": [
29
+ "opencode",
30
+ "plugin",
31
+ "gitbutler",
32
+ "git",
33
+ "branches",
34
+ "ai"
35
+ ],
36
+ "author": "gaboe",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/gaboe/opencode-gitbutler.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/gaboe/opencode-gitbutler/issues"
44
+ },
45
+ "homepage": "https://github.com/gaboe/opencode-gitbutler#readme",
46
+ "dependencies": {
47
+ "@opencode-ai/plugin": "^1.1.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@opencode-ai/plugin": ">=1.1.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/bun": "^1.3.8",
54
+ "typescript": "^5.3.0"
55
+ }
56
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from "child_process";
4
+
5
+ try {
6
+ execSync("but --version", { stdio: "pipe" });
7
+ console.log("✓ GitButler CLI found");
8
+ } catch {
9
+ console.warn("⚠ GitButler CLI not found. Install with: brew install gitbutler");
10
+ }
@@ -0,0 +1,275 @@
1
+ ---
2
+ name: but
3
+ version: 0.19.0
4
+ description: Commit, push, branch, and manage version control. Use for git commit, git status, git push, git diff, creating branches, staging files, editing history, pull requests, or any git/version control operation. Replaces git write commands with 'but' - always use this instead of raw git.
5
+ author: GitButler Team
6
+ ---
7
+
8
+ # GitButler CLI Skill
9
+
10
+ Help users work with GitButler CLI (`but` command) in workspace mode.
11
+
12
+ ## New Session Workflow
13
+
14
+ **EVERY new agent session that involves code changes MUST follow this flow:**
15
+
16
+ 1. **Sync first** → `but pull` to get latest upstream changes (prevents conflicts and stale base)
17
+ 2. **Check state** → `but status` to see existing branches and unstaged changes
18
+ 3. **Decide branch** →
19
+ - If an existing branch matches the task → reuse it (it's already applied)
20
+ - If this is new work → `but branch new <task-name>` (e.g. `feat/add-auth`, `fix/login-bug`)
21
+ - If you need to resume unapplied work → `but apply <branch>`
22
+ 4. **Make changes** → Edit files as needed
23
+ 5. **Stage & commit** → `but commit <branch> -m "message" --changes <id>,<id>`
24
+ 6. **Refine** → Use `but absorb` or `but squash` to clean up history
25
+ 7. **Push when ready** → `but push <branch>`
26
+ 8. **Create PR** → `but pr new <branch> -t` (uses default target branch)
27
+
28
+ **Branch naming**: Use conventional prefixes: `feat/`, `fix/`, `chore/`, `refactor/`
29
+
30
+ **Commit early, commit often.** Don't hesitate to create commits - GitButler makes editing history trivial. You can always `squash`, `reword`, or `absorb` changes into existing commits later. Small atomic commits are better than large uncommitted changes.
31
+
32
+ ## After Using Write/Edit Tools
33
+
34
+ When ready to commit:
35
+
36
+ 1. Run `but status --json` to see uncommitted changes and get their CLI IDs
37
+ 2. Commit the relevant files directly: `but commit <branch> -m "message" --changes <id>,<id>`
38
+
39
+ You can batch multiple file edits before committing - no need to commit after every single change.
40
+
41
+ ## Critical Concept: Workspace Model
42
+
43
+ **GitButler ≠ Traditional Git**
44
+
45
+ - **Traditional Git**: One branch at a time, switch with `git checkout`
46
+ - **GitButler**: Multiple stacks simultaneously in one workspace, changes assigned to stacks
47
+
48
+ **This means:**
49
+
50
+ - ❌ Don't use `git status`, `git commit`, `git checkout`
51
+ - ✅ Use `but status`, `but commit`, `but` commands
52
+ - ✅ Read-only git commands are fine (`git log`, `git diff`)
53
+
54
+ ## Hard Safety Rules (Non-Negotiable)
55
+
56
+ 1. **Never discard changes you did not create.**
57
+ - `zz` (unassigned) often contains work from other sessions/agents/users.
58
+ - If unrelated changes exist, leave them untouched and ask before any discard action.
59
+ 2. **Never leave your own changes in `zz` at the end of work.**
60
+ - After edits, run `but status --json` and move your file/hunk IDs to the correct branch via `but stage` or `but commit --changes`.
61
+ 3. **Validate branch ownership before commit.**
62
+ - Confirm each changed file/hunk belongs to the intended branch/task, then commit only those IDs.
63
+
64
+ ## Quick Start
65
+
66
+ **Installation:**
67
+
68
+ ```bash
69
+ curl -sSL https://gitbutler.com/install.sh | sh
70
+ but setup # Initialize in your repo
71
+ but skill install --path <path> # Install/update skill (agents use --path with known location)
72
+ ```
73
+
74
+ **Note for AI agents:**
75
+ - When installing or updating this skill programmatically, always use `--path` to specify the exact installation directory. The `--detect` flag requires user interaction if multiple installations exist.
76
+ - **Use `--json` flag for all commands** to get structured, parseable output. This is especially important for `but status --json` to reliably parse workspace state.
77
+
78
+ **Core workflow:**
79
+
80
+ ```bash
81
+ but status --json # Always start here - shows workspace state (JSON for agents)
82
+ but branch new feature # Create new stack for work
83
+ # Make changes...
84
+ but commit <branch> -m "…" --changes <id>,<id> # Commit specific files by CLI ID
85
+ but push <branch> # Push to remote
86
+ ```
87
+
88
+ ## Essential Commands
89
+
90
+ For detailed command syntax and all available options, see [references/reference.md](references/reference.md).
91
+ For a hands-on learning guide, see [references/tutorial.md](references/tutorial.md).
92
+ For a one-page quick lookup, see [references/cheatsheet.md](references/cheatsheet.md).
93
+
94
+ **IMPORTANT for AI agents:** Add `--json` flag to all commands for structured, parseable output.
95
+
96
+ **Understanding state:**
97
+
98
+ - `but status --json` - Overview (START HERE, always use --json for agents)
99
+ - `but status --json -f` - Overview with full file lists (use when you need to see all changed files)
100
+ - `but show <id> --json` - Details about commit/branch
101
+ - `but diff <id>` - Show diff
102
+
103
+ **Flags explanation:**
104
+ - `--json` - Output structured JSON instead of human-readable text (always use for agents)
105
+ - `-f` - Include detailed file lists in status output (combines with --json: `but status --json -f`)
106
+
107
+ **Organizing work:**
108
+
109
+ - `but branch new <name>` - Independent branch
110
+ - `but branch new <name> -a <anchor>` - Stacked branch (dependent)
111
+ - `but stage <file> <branch>` - Pre-assign file to branch (optional, for organizing before commit)
112
+
113
+ **Making changes:**
114
+
115
+ - `but commit <branch> -m "msg" --changes <id>,<id>` - Commit specific files or hunks (recommended)
116
+ - `but commit <branch> -m "msg" -p <id>,<id>` - Same as above, using short flag
117
+ - `but commit <branch> -m "msg"` - Commit ALL uncommitted changes to branch
118
+ - `but commit <branch> --only -m "msg"` - Commit only pre-staged changes (cannot combine with --changes)
119
+ - `but amend <file-id> <commit-id>` - Amend file into specific commit (explicit control)
120
+ - `but absorb <file-id>` - Absorb file into auto-detected commit (smart matching)
121
+ - `but absorb <branch-id>` - Absorb all changes staged to a branch
122
+ - `but absorb` - Absorb ALL uncommitted changes (use with caution)
123
+
124
+ **Getting IDs for --changes:**
125
+ - **File IDs**: `but status --json` - commit entire files
126
+ - **Hunk IDs**: `but diff --json` - commit individual hunks (for fine-grained control when a file has multiple changes)
127
+
128
+ **Editing history:**
129
+
130
+ - `but rub <source> <dest>` - Universal edit (stage/amend/squash/move)
131
+ - `but squash <commits>` - Combine commits
132
+ - `but reword <id>` - Change commit message/branch name
133
+
134
+ **Remote operations:**
135
+
136
+ - `but pull` - Update with upstream
137
+ - `but push [branch]` - Push to remote
138
+ - `but pr new <branch>` - Push and create pull request (auto-pushes, no need to push first)
139
+ - `but pr new <branch> -m "Title..."` - Inline PR message (first line is title, rest is description)
140
+ - `but pr new <branch> -F pr_message.txt` - PR message from file (first line is title, rest is description)
141
+ - For stacked branches, the custom message (`-m` or `-F`) only applies to the selected branch; dependent branches use defaults
142
+
143
+ ## Key Concepts
144
+
145
+ For deeper understanding of the workspace model, dependency tracking, and philosophy, see [references/concepts.md](references/concepts.md).
146
+
147
+ **CLI IDs**: Every object gets a short ID (e.g., `c5` for commit, `bu` for branch). Use these as arguments.
148
+
149
+ **Parallel vs Stacked branches**:
150
+
151
+ - Parallel: Independent work that doesn't depend on each other
152
+ - Stacked: Dependent work where one feature builds on another
153
+
154
+ **The `but rub` primitive**: Core operation that does different things based on what you combine:
155
+
156
+ - File + Branch → Stage
157
+ - File + Commit → Amend
158
+ - Commit + Commit → Squash
159
+ - Commit + Branch → Move
160
+
161
+ ## Workflow Examples
162
+
163
+ For complete step-by-step workflows and real-world scenarios, see [references/examples.md](references/examples.md).
164
+
165
+ **Starting independent work:**
166
+
167
+ ```bash
168
+ but status --json
169
+ but branch new api-endpoint
170
+ but branch new ui-update
171
+ # Make changes, then commit specific files to appropriate branches
172
+ but status --json # Get file CLI IDs
173
+ but commit api-endpoint -m "Add endpoint" --changes <api-file-id>
174
+ but commit ui-update -m "Update UI" --changes <ui-file-id>
175
+ ```
176
+
177
+ **Committing specific hunks (fine-grained control):**
178
+
179
+ ```bash
180
+ but diff --json # See hunk IDs when a file has multiple changes
181
+ but commit <branch> -m "Fix first issue" --changes <hunk-id-1>
182
+ but commit <branch> -m "Fix second issue" --changes <hunk-id-2>
183
+ ```
184
+
185
+ **Cleaning up commits:**
186
+
187
+ ```bash
188
+ but absorb # Auto-amend changes
189
+ but status --json # Verify absorb result
190
+ but squash <branch> # Squash all commits in branch
191
+ ```
192
+
193
+ **Resolving conflicts:**
194
+
195
+ ```bash
196
+ but resolve <commit> # Enter resolution mode
197
+ # Fix conflicts in editor
198
+ but resolve finish # Complete resolution
199
+ ```
200
+
201
+ **Managing workspace:**
202
+
203
+ ```bash
204
+ but config target origin/test # Set default PR target (requires unapply all branches first)
205
+ but unapply <branch> # Remove branch from workspace (keeps commits)
206
+ but apply <branch> # Bring branch back into workspace
207
+ but teardown # Exit GitButler mode → normal git
208
+ but setup # Re-enter GitButler mode
209
+ but discard <ids> # Discard unstaged changes
210
+ ```
211
+
212
+ ## Post-Merge PR Flow
213
+
214
+ After a PR is squash-merged on GitHub, follow this exact sequence:
215
+
216
+ ```bash
217
+ but unapply <merged-branch> # MUST do BEFORE pull - prevents orphan branch errors
218
+ but pull # Pull merged changes from remote
219
+ ```
220
+
221
+ **Critical**: If you `but pull` before unapplying the merged branch, GitButler will error with orphan branch conflicts. Always unapply first.
222
+
223
+ **If `but unapply` fails** (branch already gone from workspace after remote deletion with `--delete-branch`), `but pull` may also fail with "resolution mismatch" errors because the ghost stack still exists internally. In this case, the GitButler desktop app can handle it — tell the user to run `but pull` from the GUI. Alternatively, use `but teardown` → `but setup` → `but config target origin/<branch>` to reset.
224
+
225
+ **After `but teardown` → `but setup`**: Target config resets. Run `but config target origin/<branch>` again.
226
+
227
+ ## Using `--no-hooks` Safely
228
+
229
+ When pre-commit hooks fail on pre-existing errors unrelated to your changes, use `--no-hooks`. But this skips the formatter too:
230
+
231
+ ```bash
232
+ bun run format # Format FIRST
233
+ but commit <branch> -m "msg" --changes <ids> --no-hooks # Then commit without hooks
234
+ ```
235
+
236
+ Alternatively, commit normally and absorb formatter fixes:
237
+
238
+ ```bash
239
+ but commit <branch> -m "msg" --changes <ids> # Commit (hooks may fix formatting)
240
+ but absorb # Absorb any auto-formatted changes
241
+ ```
242
+
243
+ ## Known Issues & Workarounds
244
+
245
+ | Issue | What happens | Workaround |
246
+ |-------|-------------|------------|
247
+ | `but resolve` loses target config | After entering resolve mode, `but config target` resets to "not set" | Run `but config target origin/<branch>` again after `but resolve finish`. If finish fails, do `git checkout gitbutler/workspace` → `but teardown` → `but setup` |
248
+ | `but absorb` hunk lock | Absorb assigns hunk to wrong commit when it's locked by another commit on a different branch | Use `but amend <file> <commit>` for explicit control instead of absorb |
249
+ | `but pr new` has no `--base` flag | Always creates PR against default target | Set target first: `but config target origin/<branch>` |
250
+ | `but config target` requires unapply | Cannot change target with applied branches | `but unapply` all → change target → `but apply` |
251
+ | `but config forge auth` is interactive | Cannot run in non-interactive agent mode | User must run in terminal + grant org access on GitHub |
252
+ | `but commit` pre-commit hook fails | Hook fails on pre-existing errors unrelated to your changes | `but commit --no-hooks` if errors are not from your changes. **Always `bun run format` first** since `--no-hooks` skips the formatter |
253
+ | `but branch delete` last segment | Cannot delete if it would leave anonymous segment | Use `but unapply` instead of delete |
254
+ | `but stage` prefix matching | Branch name can be abbreviated | `but stage <id> ch` works for `chore/gitbutler-setup` |
255
+ | `but discard` hunk range error | Discarding file-level changes sometimes fails with hunk range errors | Use `git checkout -- <file>` instead of `but discard` for file-level discards |
256
+ | `but teardown` + `but setup` resets target | After teardown/setup cycle, target config is lost | Run `but config target origin/<branch>` again after setup |
257
+ | Lefthook `pre-commit.old` accumulates | Lefthook creates `pre-commit.old` backup that conflicts on next install | Add `rm -f .git/hooks/pre-commit.old` to `prepare` script in package.json |
258
+ | `but pull` before unapply | Pulling with merged branches still applied causes orphan errors | **Always** `but unapply <merged-branch>` before `but pull` |
259
+ | `but unapply` after remote branch deletion | `but unapply` fails with "branch not found" when remote deleted the branch (e.g. `--delete-branch` on merge), and subsequent `but pull` fails with "resolution mismatch" | Use GitButler desktop app to pull, or `but teardown` → `but setup` → `but config target origin/<branch>` |
260
+
261
+ ## Critical Safety Rules
262
+
263
+ 1. **NEVER discard changes you didn't create.** Unassigned changes in `zz` may belong to other agents, sessions, or the user. Always ask the user before running `but discard` or `git checkout --` on any change you don't recognize. In GitButler workspace, multiple actors work in parallel — discarding "stale" or "already merged" changes is a destructive assumption.
264
+ 2. **Always assign your changes to a branch immediately.** Don't leave edits sitting in `zz` (unassigned). After editing files, stage them to your working branch with `but stage <file-id> <branch>` or commit directly with `--changes`.
265
+
266
+ ## Guidelines
267
+
268
+ 1. Always start with `but status --json` to understand current state (agents should always use `--json`)
269
+ 2. Create a new stack for each independent work theme
270
+ 3. Use `--changes` to commit specific files directly - no need to stage first
271
+ 4. **Commit early and often** - don't wait for perfection. Unlike traditional git, GitButler makes editing history trivial with `absorb`, `squash`, and `reword`. It's better to have small, atomic commits that you refine later than to accumulate large uncommitted changes.
272
+ 5. **Use `--json` flag for ALL commands** when running as an agent - this provides structured, parseable output instead of human-readable text
273
+ 6. Use `--dry-run` flags (push, absorb) when unsure
274
+ 7. **Run `but pull` frequently** — at session start, before creating branches, and before pushing. Stale workspace = merge conflicts
275
+ 8. When updating this skill, use `but skill install --path <known-path>` to avoid prompts