@senad-d/branchme 0.3.0 → 0.3.1
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/CHANGELOG.md +11 -3
- package/README.md +62 -24
- package/SECURITY.md +27 -17
- package/docs/PROJECT_DEFINITION_BRIEF.md +30 -19
- package/docs/SMOKE_TEST.md +13 -4
- package/docs/STRUCTURE.md +20 -12
- package/docs/TUI_CAPTURE.md +41 -31
- package/package.json +3 -2
- package/src/commands/branchme-command.ts +13 -3
- package/src/constants.ts +13 -0
- package/src/git-integration.ts +27 -6
- package/src/git-landing.ts +63 -11
- package/src/git-workflow.ts +136 -0
- package/src/git.ts +279 -3
- package/src/github.ts +115 -0
- package/src/tools/branchme-tools.ts +84 -12
- package/src/tools/workflow-tools.ts +86 -0
- package/src/types.ts +55 -0
- package/src/ui/branchme-panel.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.3.
|
|
3
|
+
## 0.3.1 - Unreleased
|
|
4
4
|
|
|
5
|
+
- Added bounded `list_branches` discovery with local/remote-tracking identity, cached upstream ahead/behind counts, symbolic refs, and worktree occupancy.
|
|
6
|
+
- Added `track_branch` to fetch and verify a new local tracking checkout, and `update_from_base` to merge a freshly fetched base into the current feature without rewriting published history. Both require clean idle checkouts and narrow fetch scope.
|
|
7
|
+
- Added `pull_request_status` for validated same-repository open/closed/merged PR lifecycle and commit identities. PR creation now returns `created` or `existing`, preserves existing PR fields, and rechecks HTTP 422 creation races without repeating the POST.
|
|
8
|
+
- Added optional `land_branch.pullRequestNumber` for squash/rebase cleanup: exact merged head/base and merge-commit containment are verified against the configured GitHub remote while retaining local deletion leases and truthful graph ancestry.
|
|
9
|
+
- Corrected stale fetch, ancestry, worktree base-ref, tool-count, and merge-policy documentation; expanded strict schema, real-Git, mocked GitHub, and runtime smoke coverage.
|
|
10
|
+
- Hardened tracking/base-update/landing fetches against symbolic destinations, preserved upstream configuration checks when missing cached refs are restored, blocked unsafe initialization environments and inconclusive discovery, retained inspection guidance after initialization verification failures, and redacted PR cancellation errors before truncation.
|
|
11
|
+
|
|
12
|
+
- Added `init_repository` for verified initialization of pi's exact current directory as a non-bare Git repository with an optional initial branch (default `main`). It rejects reinitialization and nested repositories, accepts no path or repository-mode controls, creates no commit or project files, and verifies the in-place `.git` directory and unborn branch.
|
|
5
13
|
- Added `land_branch` for deterministic post-host-merge cleanup in one queued call: explicit cwd-independent Git routing from the primary repository root, targeted remote fetch and captured ancestry proof, force-free linked-worktree removal including ignored residue, expected-HEAD-leased local source deletion against the fetched remote target, and independent final target sync (`fetch-refspec`, `pull-ff`, `skipped-dirty`, or `noop`).
|
|
6
14
|
- Landing refuses an unmerged source or a cwd inside the removal target, never switches branches or prunes, preserves dirty checkouts and remote branches, and supports absent-branch/worktree retries. Structured per-step receipts report redacted diagnostics, deleted ignored top-level paths, actual before/after refs, and ahead/behind counts; failed or unattempted sync is never reported as a fast-forward.
|
|
7
15
|
- Added bare-origin real-Git landing regression tests for the parked-main/dirty-primary incident, clean and dirty target checkouts, unmerged and dirty-worktree refusals, cwd safety, idempotence, lease races, false-success command reporting, divergence, configuration-driven pruning, and redaction.
|
|
@@ -10,13 +18,13 @@
|
|
|
10
18
|
- Added optional `remote`/`branch` parameters to `fetch_branch` for a targeted fetch of one exact remote branch into its remote-tracking ref (default remote `origin`; `remote` requires `branch`) without touching local branches, the working tree, or the current branch's upstream configuration; the no-argument behavior is unchanged.
|
|
11
19
|
- Added an optional read-only `baseRef` parameter to `create_worktree` for `branchMode: "new"`, starting the new branch from an exact local branch, remote-tracking ref, or full commit regardless of the current checkout's branch, dirt, or staleness; the default from-`HEAD` behavior is unchanged.
|
|
12
20
|
- Implemented the `branchme` informational slash command with help aliases.
|
|
13
|
-
-
|
|
21
|
+
- Registered nineteen strict BranchMe tools, including `list_branches`, `track_branch`, `update_from_base`, and `pull_request_status`, alongside: `init_repository`, `branch_status`, `change_branch`, `fetch_branch`, `pull_branch`, `rebase_branch`, `integrate_branch`, `retire_branch`, `land_branch`, `create_branch`, `push_branch`, `pull_request`, `list_worktrees`, `create_worktree`, and `remove_worktree`; merge-continuation tools are intentionally absent.
|
|
14
22
|
- Added argv-style git helpers for repository status, branch validation/creation/switching, clean-worktree preflight, upstream detection, configured-upstream fetch, fast-forward-only current-branch pull, current-branch rebase with automatic abort on failure, verified local branch integration, current-branch push/publish, and bounded NUL-delimited worktree discovery.
|
|
15
23
|
- Added `integrate_branch` for exact local source-to-target integration from a clean already-current target control worktree. It rejects branch-specific target merge options that could alter the fixed policy and supports already-integrated, fast-forward, verified normal merge-commit, and automatically aborted/restored conflict outcomes with before/after ref and ancestry proofs, while exposing no fetch, push, reset, merge-message, or continuation controls.
|
|
16
24
|
- Added `retire_branch` for explicit deletion of one exact unoccupied local branch ref after full expected-`HEAD` matching and captured target-ancestry verification. It uses `git update-ref --no-deref -d` with an expected-old-value lease, requires explicit force authorization for unmerged history, leaves branch configuration and remote/remote-tracking refs untouched, and reports uncertain postconditions for manual inspection without reset rollback.
|
|
17
25
|
- Added optional targeted `branch_status.ancestry` verification for captured local source/target commits without changing automatic Git context.
|
|
18
26
|
- Added verified linked-worktree creation for a new branch from current `HEAD` or an unoccupied existing local branch, returning a structured ready handoff with the exact canonical absolute cwd and local branch for a caller-managed separate agent session.
|
|
19
|
-
- Added force-free removal for exact, verified, clean linked worktrees while preserving and re-verifying the local branch at its captured commit; main, current, dirty,
|
|
27
|
+
- Added force-free removal for exact, verified, clean linked worktrees while preserving and re-verifying the local branch at its captured commit; main, current, dirty, detached, locked, prunable/missing, bare, and foreign worktrees are rejected. Ignored residue is protected by default and can be deleted only with explicit `deleteIgnored: true`, with redacted top-level paths returned in `deletedIgnoredPaths`.
|
|
20
28
|
- Added canonical absolute-path and lossless-identity validation before worktree mutations, including existing-destination, nested-worktree, common-Git-directory, repository-membership, redaction, escaping, and truncation boundaries. BranchMe does not copy ignored/untracked files or start/switch Pi sessions.
|
|
21
29
|
- Added GitHub repository resolution, environment-token and local `.env` token fallback handling, REST pull request creation, response validation, and token redaction.
|
|
22
30
|
- Added opt-in `BRANCHME_PR_AUTOFILL` support for omitted PR fields, including current/default branch inference, bounded, Markdown-safe, and token-redacted title/body generation from commit subjects, and a non-draft default.
|
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
|
|
14
|
-
<br />
|
|
13
|
+
Git repository initialization, branch, worktree, integration, retirement, and pull request tools for <a href="https://pi.dev">pi</a>.
|
|
14
|
+
<br />Initialize a repository, inspect branch state, manage verified linked worktrees, integrate or retire local branches, push, and open GitHub PRs from pi prompts.
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
BranchMe is a Pi extension for safe branch and worktree workflow automation. Before each agent run, it appends a bounded, read-only snapshot of the current Git repository to the system prompt. It also adds an informational `/branchme` command and
|
|
19
|
+
BranchMe is a Pi extension for safe Git repository, branch, and worktree workflow automation. Before each agent run, it appends a bounded, read-only snapshot of the current Git repository to the system prompt. It also adds an informational `/branchme` command and nineteen agent-callable tools that initialize a repository, refresh state, manage, integrate, and retire local branches, inspect/create/remove linked worktrees, push the current branch, and create GitHub pull requests.
|
|
20
20
|
|
|
21
21
|
<table align="center">
|
|
22
22
|
<tr>
|
|
@@ -30,14 +30,14 @@ BranchMe is a Pi extension for safe branch and worktree workflow automation. Bef
|
|
|
30
30
|
</table>
|
|
31
31
|
|
|
32
32
|
- **Context-aware:** every agent run starts with bounded branch, working-tree, related-PR, and recent-commit metadata; repository metadata is untrusted data, not instructions.
|
|
33
|
-
- **Repository-scoped:** Git and GitHub operations resolve from the checkout where pi is running. Linked worktree directories may be outside that checkout, but must be verified members of the same repository.
|
|
33
|
+
- **Repository-scoped:** `init_repository` can initialize only pi's exact current working directory when it is not already inside a repository. Other Git and GitHub operations resolve from the checkout where pi is running. Linked worktree directories may be outside that checkout, but must be verified members of the same repository.
|
|
34
34
|
- **Explicit history rewrites:** `rebase_branch` runs only when explicitly requested, requires a clean current branch with an upstream, disables autostash and multi-ref updates, and automatically attempts to abort on failure.
|
|
35
35
|
- **Verified worktree handoff:** `create_worktree` verifies path, branch, `HEAD`, and cleanliness before returning an absolute `handoff.cwd`; starting another Pi session or subagent there remains the caller's responsibility.
|
|
36
|
-
- **Commit-safe:** context collection is read-only, and BranchMe never stages files, creates user-authored commits, accepts or generates commit messages, force-pushes, resets, or edits files directly.
|
|
37
|
-
- **Strict tools:** tool schemas reject undocumented properties such as `stash`, `discard`, `owner`, `repo`, `path
|
|
36
|
+
- **Commit-safe:** context collection is read-only, and BranchMe never stages files, creates user-authored commits, accepts or generates commit messages, force-pushes, resets, or edits files directly. Explicit `integrate_branch` and `update_from_base` calls may let Git create a standard merge commit for divergent histories.
|
|
37
|
+
- **Strict tools:** tool schemas reject undocumented properties such as `stash`, `discard`, `owner`, `repo`, or `path`; `baseRef` is supported only by `create_worktree` in new-branch mode; the only force decision is the required boolean on `retire_branch`, and every tool accepts only its documented fields.
|
|
38
38
|
- **PR-ready:** create GitHub pull requests from existing local branches after verifying the `headBranch` matches GitHub and the base is visible. PR fields can stay explicit, or configured autofill can derive omitted fields from the current branch, default branch, and commit subjects.
|
|
39
39
|
|
|
40
|
-
> **Security:** pi packages run with your full system permissions. BranchMe runs local `git` commands, may create or remove verified linked-worktree directories outside the active checkout, may integrate local history or retire one exact local branch ref, may make an automatic authenticated GitHub request to find a related open pull request, can update branches and remotes, and can create GitHub pull requests. Repository-configured hooks—including `reference-transaction` hooks invoked by retirement—merge drivers, filters, and signing policy may run commands or contact networks outside BranchMe's direct argv guarantees. Read [`SECURITY.md`](SECURITY.md).
|
|
40
|
+
> **Security:** pi packages run with your full system permissions. BranchMe runs local `git` commands, may initialize `.git` metadata in the current directory, may create or remove verified linked-worktree directories outside the active checkout, may integrate local history or retire one exact local branch ref, may make an automatic authenticated GitHub request to find a related open pull request, can update branches and remotes, and can create GitHub pull requests. Repository-configured hooks—including `reference-transaction` hooks invoked by retirement—merge drivers, filters, and signing policy may run commands or contact networks outside BranchMe's direct argv guarantees. Read [`SECURITY.md`](SECURITY.md).
|
|
41
41
|
|
|
42
42
|
## Table of Contents
|
|
43
43
|
|
|
@@ -79,6 +79,10 @@ A normal prompt can use the automatic start-of-run snapshot without a tool call.
|
|
|
79
79
|
Refresh the repository state with branch_status, then create a branch named feature/update-docs with create_branch.
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
When pi starts in a directory that is not already inside a Git repository, explicitly ask it to call `init_repository`. The tool defaults to an unborn `main` branch, or accepts an optional `initialBranch`; it creates no commit, README, `.gitignore`, remote, or user configuration.
|
|
83
|
+
|
|
84
|
+
Use `list_branches` to discover local and cached remote-tracking branches, upstream ahead/behind counts, and worktree occupancy. To join an existing remote branch in the active checkout, use `track_branch` rather than creating from the wrong `HEAD`.
|
|
85
|
+
|
|
82
86
|
A typical BranchMe flow is:
|
|
83
87
|
|
|
84
88
|
1. Use the automatic snapshot to understand state at the start of the agent run.
|
|
@@ -88,7 +92,9 @@ A typical BranchMe flow is:
|
|
|
88
92
|
5. Create from the updated `HEAD` with `create_branch`.
|
|
89
93
|
6. Make edits and commit outside BranchMe.
|
|
90
94
|
7. Push the current branch with `push_branch`.
|
|
91
|
-
8. After `push_branch` completes and GitHub can see the branches, create a pull request with `pull_request`.
|
|
95
|
+
8. After `push_branch` completes and GitHub can see the branches, create or reuse a matching open pull request with `pull_request`.
|
|
96
|
+
9. While working, use `update_from_base({ baseBranch: "main" })` to fetch and merge the remote base into the clean feature branch without rewriting published history or changing its upstream.
|
|
97
|
+
10. Inspect the PR with `pull_request_status({ number: 123 })`. Merge it on GitHub outside BranchMe, then use `land_branch` from the primary checkout. Supply `pullRequestNumber: 123` for squash/rebase merge evidence.
|
|
92
98
|
|
|
93
99
|
For isolated work, a specialized Git subagent can use the explicit worktree workflow:
|
|
94
100
|
|
|
@@ -96,16 +102,16 @@ For isolated work, a specialized Git subagent can use the explicit worktree work
|
|
|
96
102
|
2. Ask the user to provide or approve an exact absolute destination and call `create_worktree` with `branchMode: "new"` or `"existing"`.
|
|
97
103
|
3. Wait for the result and require `details.handoff.ready === true`.
|
|
98
104
|
4. Have the caller or orchestrator start a **separate** Pi session or subagent with its working directory set to the returned absolute `details.handoff.cwd`.
|
|
99
|
-
5. After that session finishes, remove or preserve any staged, unstaged, untracked,
|
|
105
|
+
5. After that session finishes, remove or preserve any staged, unstaged, untracked, or unmerged files, then explicitly call `remove_worktree` if removal was requested. Ignored residue is protected by default; after preserving anything valuable, explicitly set `deleteIgnored: true` to delete it with the worktree. Removal retains the local branch.
|
|
100
106
|
6. If the user separately asks to retire that retained branch, obtain its fresh exact `HEAD`, verify it against an exact local target, and call `retire_branch` only after worktree removal has completed.
|
|
101
107
|
|
|
102
|
-
After a pull request merges on the host, use [`land_branch`](#post-merge-cleanup-in-one-call) from the repository root instead of composing fetch/removal/retirement/sync calls. This
|
|
108
|
+
After a pull request merges on the host, use [`land_branch`](#post-merge-cleanup-in-one-call) from the repository root instead of composing fetch/removal/retirement/sync calls. This combined workflow deletes ignored worktree residue and retires the source branch. Standalone `remove_worktree` always retains the branch and protects ignored residue unless `deleteIgnored: true` explicitly authorizes its deletion.
|
|
103
109
|
|
|
104
110
|
BranchMe does not change the active Pi process's cwd, start Pi or other processes, create sessions, or copy `.env` or other ignored/untracked files. For credentials needed by agents in linked worktrees, prefer process-level environment variables rather than copying repository-root secrets.
|
|
105
111
|
|
|
106
112
|
To refresh the current branch's configured remote-tracking ref without changing the local branch or working tree, use `fetch_branch` with no arguments. To refresh another remote-tracking ref — for example `origin/main` after a pull request merged on GitHub — call `fetch_branch` with `branch` (and optional `remote`, default `origin`); the targeted fetch never touches local branches, the working tree, or the current branch's upstream configuration. To reconcile the clean current branch by rewriting its local commits, run `fetch_branch`, wait for it to complete, and then run `rebase_branch`. The no-argument `fetch_branch` and `rebase_branch` require a configured upstream; `rebase_branch` automatically attempts `git rebase --abort` if rebasing fails.
|
|
107
113
|
|
|
108
|
-
BranchMe is tool-based. The slash command is informational only and never changes or updates branches, creates or removes worktrees, changes cwd, starts processes/sessions, fetches, rebases, pushes, commits, stages, edits files, or opens pull requests.
|
|
114
|
+
BranchMe is tool-based. The slash command is informational only and never initializes repositories, changes or updates branches, creates or removes worktrees, changes cwd, starts processes/sessions, fetches, rebases, pushes, commits, stages, edits files, or opens pull requests.
|
|
109
115
|
|
|
110
116
|
---
|
|
111
117
|
|
|
@@ -141,7 +147,7 @@ pi install /absolute/path/to/branchme
|
|
|
141
147
|
|
|
142
148
|
## Repository and GitHub Setup
|
|
143
149
|
|
|
144
|
-
BranchMe does not bundle Git
|
|
150
|
+
BranchMe does not bundle Git. Start pi inside an existing repository, or start it in an existing non-repository directory and explicitly request `init_repository`:
|
|
145
151
|
|
|
146
152
|
```bash
|
|
147
153
|
cd /path/to/your/git/repo
|
|
@@ -150,6 +156,17 @@ git remote get-url origin
|
|
|
150
156
|
pi
|
|
151
157
|
```
|
|
152
158
|
|
|
159
|
+
For a new repository:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
mkdir my-project
|
|
163
|
+
cd my-project
|
|
164
|
+
pi
|
|
165
|
+
# Ask: Initialize this directory with init_repository.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Initialization rejects existing or nested repositories, defaults to an unborn `main` branch, and creates no commit or project files. Git environment overrides that redirect repository paths or discovery must be unset. Failed or unrecognized repository discovery is not treated as permission to initialize; uncertain postconditions require inspection without automatic cleanup.
|
|
169
|
+
|
|
153
170
|
For pull requests, the repository must resolve to GitHub from local `origin` and/or `GITHUB_REPOSITORY`:
|
|
154
171
|
|
|
155
172
|
```bash
|
|
@@ -226,12 +243,17 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
226
243
|
|
|
227
244
|
| Tool | Schema | Behavior |
|
|
228
245
|
| --- | --- | --- |
|
|
246
|
+
| `list_branches` | `{}` | Read up to 200 local and cached remote-tracking branches, commits, current/upstream state, ahead/behind counts, symbolic refs, and worktree occupancy. No fetch. Raw refs are limited to 128 KiB and text to 4,000 characters; omitted entries are reported. Names/paths are display metadata, not guaranteed executable identities. |
|
|
247
|
+
| `track_branch` | `{ "branchName": string, "remote"?: string, "remoteBranch"?: string }` | Fetch one exact remote branch and create/check out a new local tracking branch. Remote defaults to `origin`; remoteBranch defaults to branchName. Requires a clean idle checkout; verifies HEAD and upstream. |
|
|
248
|
+
| `update_from_base` | `{ "baseBranch": string, "remote"?: string }` | Fetch the exact remote base (remote defaults to `origin`) and merge its captured commit into the clean current feature. Uses verified normal-merge policy and automatic conflict abort; never rebases, pushes, or changes upstream configuration. |
|
|
249
|
+
| `pull_request_status` | `{ "number"?: integer, "headBranch"?: string }` | Read exact same-repository PR lifecycle and head/base/merge identities. Number and headBranch are mutually exclusive. Without number, returns the most recently updated PR for headBranch or the current branch. Does not certify CI checks or review approvals. |
|
|
250
|
+
| `init_repository` | `{ "initialBranch"?: string }` | Initializes only pi's exact current working directory as a verified non-bare Git repository with an unborn branch (default `main`). Rejects existing or nested repositories and accepts no path, bare, template, shared, remote, commit, or project-file controls. |
|
|
229
251
|
| `branch_status` | `{ "ancestry"?: { "sourceBranch": string, "targetBranch": string } }` | Explicitly refreshes the same bounded context used at agent start. An optional strict ancestry query captures both exact HEADs and reports whether the source commit is an ancestor of the target commit; each endpoint may be an exact local branch or a remote-tracking ref such as `origin/main` (a local branch of the same name takes precedence). It is read-only and never checks out or resets remote-tracking refs; automatic Git context does not run ancestry queries. |
|
|
230
252
|
| `list_worktrees` | `{}` | Runs a bounded, read-only inventory of the current repository's main and linked worktrees, including path, branch/detached state, `HEAD`, current/main, locked, prunable, and omitted-entry details. Automatic Git context does not include this inventory. |
|
|
231
253
|
| `create_worktree` | `{ "worktreePath": string, "branchName": string, "branchMode": "new" \| "existing", "baseRef"?: string }` | Creates and verifies a linked worktree at an explicitly approved absolute path. `new` creates a local branch from current `HEAD`, or from the optional read-only `baseRef` (an exact local branch, remote-tracking ref such as `origin/main`, or full commit) regardless of the current checkout's branch, dirt, or staleness; `existing` requires an existing local branch not checked out elsewhere and rejects `baseRef`. It returns a ready handoff with the exact canonical absolute cwd and local branch identity. |
|
|
232
|
-
| `remove_worktree` | `{ "worktreePath": string }` | Force-free removal of an explicitly selected, verified clean linked worktree.
|
|
254
|
+
| `remove_worktree` | `{ "worktreePath": string, "deleteIgnored"?: boolean }` | Force-free removal of an explicitly selected, verified clean linked worktree. Ignored residue is refused by default; `deleteIgnored: true` explicitly deletes it and reports its top-level paths. The local branch remains at the same commit. |
|
|
233
255
|
| `retire_branch` | `{ "branchName": string, "expectedHead": string, "targetBranch": string, "force": boolean }` | Deletes only the exact unoccupied local branch ref when its direct ref matches the supplied full commit ID and its relationship to the exact local target has been verified. Unmerged retirement requires explicit `force: true`; remote and remote-tracking refs are untouched. |
|
|
234
|
-
| `land_branch` | `{ "sourceBranch": string, "targetBranch": string, "remote"?: string, "worktreePath"?: string }` | Post-merge fetch, ancestry proof, linked-worktree removal including ignored residue, leased local source deletion, and independent fast-forward target sync. Default remote `origin`; omitted path finds the source's linked worktree. Returns per-step receipts. |
|
|
256
|
+
| `land_branch` | `{ "sourceBranch": string, "targetBranch": string, "remote"?: string, "worktreePath"?: string, "pullRequestNumber"?: integer }` | Post-merge fetch, ancestry or exact merged-PR proof, linked-worktree removal including ignored residue, leased local source deletion, and independent fast-forward target sync. Default remote `origin`; omitted path finds the source's linked worktree. Returns per-step receipts. |
|
|
235
257
|
| `change_branch` | `{ "branchName": string }` | Validates `branchName`, requires `refs/heads/<branchName>` to exist locally, rejects dirty worktrees, and runs `git switch <branchName>`. |
|
|
236
258
|
| `fetch_branch` | `{ "remote"?: string, "branch"?: string }` | With no arguments it requires a current branch with a configured upstream and runs `git fetch --no-tags --no-recurse-submodules <upstream-remote> <upstream-branch-ref>:<remote-tracking-ref>`. With `branch` (and optional `remote`, default `origin`; `remote` requires `branch`) it fetches that exact remote branch into `refs/remotes/<remote>/<branch>` instead. Either way only that tracking ref is refreshed without changing local branches, working-tree files, or upstream configuration. |
|
|
237
259
|
| `pull_branch` | `{}` | Requires a clean current branch with a configured upstream and runs `git pull --ff-only --no-rebase --no-autostash <upstream-remote> <upstream-branch-ref>`; divergence fails without rebasing or creating a merge commit. |
|
|
@@ -241,12 +263,18 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
241
263
|
| `push_branch` | `{}` | Pushes the current branch to its configured upstream remote with an explicit `HEAD:<upstream-branch-ref>` refspec, or publishes it with `git push --set-upstream origin <currentBranch>` when no upstream exists. |
|
|
242
264
|
| `pull_request` | `{ "headBranch"?: string, "baseBranch"?: string, "title"?: string, "body"?: string, "draft"?: boolean }` | Preflights GitHub branch visibility and verifies the GitHub `headBranch` commit matches the local branch, then creates a pull request in the resolved current repository. Omitted fields require `BRANCHME_PR_AUTOFILL=true`; branch refs must be distinct, exist locally, and cannot use `owner:branch`. |
|
|
243
265
|
|
|
244
|
-
All schemas reject additional properties. `change_branch` never accepts `baseRef`, `force`, `stash`, `discard`, `create`, `owner`, `repo`, or path inputs. `fetch_branch` accepts only the optional `remote` and `branch` pair for a targeted remote-tracking refresh (`remote` requires `branch`) and never accepts refspec, tags, prune, or force controls. `pull_branch` and `rebase_branch` have strict empty schemas and never accept a branch, remote, refspec, force, autostash, or arbitrary rebase target. `integrate_branch` requires exactly `sourceBranch` and `targetBranch`; it accepts no repository, path, remote, strategy, message, squash, signing, commit, continuation, abort, force, fetch, push, deletion, or worktree controls. `create_worktree` requires exactly `worktreePath`, `branchName`, and `branchMode`, plus an optional read-only `baseRef` start point for `branchMode: "new"`; `remove_worktree` requires
|
|
266
|
+
All schemas reject additional properties. `init_repository` accepts only optional `initialBranch`, never a path or repository-mode controls. `change_branch` never accepts `baseRef`, `force`, `stash`, `discard`, `create`, `owner`, `repo`, or path inputs. `fetch_branch` accepts only the optional `remote` and `branch` pair for a targeted remote-tracking refresh (`remote` requires `branch`) and never accepts refspec, tags, prune, or force controls. `pull_branch` and `rebase_branch` have strict empty schemas and never accept a branch, remote, refspec, force, autostash, or arbitrary rebase target. `integrate_branch` requires exactly `sourceBranch` and `targetBranch`; it accepts no repository, path, remote, strategy, message, squash, signing, commit, continuation, abort, force, fetch, push, deletion, or worktree controls. `create_worktree` requires exactly `worktreePath`, `branchName`, and `branchMode`, plus an optional read-only `baseRef` start point for `branchMode: "new"`; `remove_worktree` requires `worktreePath` and accepts optional boolean `deleteIgnored`. No worktree tool accepts force, move, prune, repair, lock, unlock, detached, orphan, remote, or refspec controls. `retire_branch` requires exactly `branchName`, a full 40- or 64-hex-character `expectedHead`, a distinct local `targetBranch`, and the boolean `force` decision; it accepts no repository, path, remote, refspec, pattern, branch list, prune, remote-delete, worktree-removal, or inferred-target control. `pull_request` never accepts `owner`, `repo`, or owner-prefixed branch refs; BranchMe resolves the repository from local `origin` and/or matching `GITHUB_REPOSITORY`. `continue_merge` and `abort_merge` are not available.
|
|
245
267
|
|
|
246
268
|
---
|
|
247
269
|
|
|
248
270
|
## Workflow and Boundaries
|
|
249
271
|
|
|
272
|
+
### PR retries and lifecycle inspection
|
|
273
|
+
|
|
274
|
+
`pull_request` first verifies GitHub branch visibility and head/local commit equality, then looks for an open PR. An exact head-commit/base match returns `outcome: "existing"` without a POST; otherwise creation returns `outcome: "created"`. Existing title, body, and draft state are preserved, not updated. A different base or stale PR head fails closed. A concurrent creation causing HTTP 422 triggers one read-only recheck, never another POST.
|
|
275
|
+
|
|
276
|
+
`pull_request_status` reads open, closed-unmerged, and merged PRs with a 10-second deadline per API request and a 64 KiB response limit. A successful empty branch lookup returns `pullRequest: null`; authentication, network, or malformed-response failures are errors rather than "no PR". Exact-number lookup is preferred for cleanup evidence. Fork PRs, PR edits/merges, review and required-check policy, clone, remote configuration/deletion, and explicit first-push destination selection remain outside this implementation.
|
|
277
|
+
|
|
250
278
|
### Automatic context and freshness
|
|
251
279
|
|
|
252
280
|
Before each agent run, BranchMe appends an **Automatic Git Context** snapshot to the existing system prompt. The snapshot contains these fields in order:
|
|
@@ -330,11 +358,21 @@ For `branchMode: "new"`, BranchMe creates the requested local branch from the cu
|
|
|
330
358
|
|
|
331
359
|
The full details also distinguish requested input from verified before/after state. Successful `handoff.cwd` and `handoff.branch` values are the exact identities verified against Git; BranchMe never substitutes `[REDACTED]`, escaped control sequences, or a truncation ellipsis in these machine-readable fields. Display content, summaries, and worktree inventory remain sanitized and bounded separately. An orchestrator may use `handoff.cwd` only after `ready` is `true`, and must start the next Pi session or subagent itself with that exact working directory. BranchMe never changes the active process's cwd or starts another process/session.
|
|
332
360
|
|
|
333
|
-
`remove_worktree` canonicalizes the approved absolute path and requires an exact match in a fresh inventory for the current repository. It accepts only a present, unlocked, non-prunable, non-bare, branch-attached linked worktree that is neither main nor current, then rejects staged, unstaged, untracked,
|
|
361
|
+
`remove_worktree` canonicalizes the approved absolute path and requires an exact match in a fresh inventory for the current repository. It accepts only a present, unlocked, non-prunable, non-bare, branch-attached linked worktree that is neither main nor current, then rejects staged, unstaged, untracked, or unmerged entries. Ignored entries are refused by default without disclosing their paths. Optional `deleteIgnored: true` explicitly authorizes deleting all ignored files and directories with the worktree; preserve `.env`, `.pi/`, dependency caches, build output, or other valuable residue first:
|
|
362
|
+
|
|
363
|
+
```json
|
|
364
|
+
{
|
|
365
|
+
"worktreePath": "/absolute/path/to/branchme-feature",
|
|
366
|
+
"deleteIgnored": true
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The bounded receipt reports only redacted top-level ignored paths in `deletedIgnoredPaths`, never contents. The canonical path and retained branch must pass the same pre-mutation lossless-identity checks used for creation. Removal uses `git worktree remove <verified-path>` without force, verifies the entry is gone, and returns the exact retained branch identity after confirming it still points to the captured commit:
|
|
334
371
|
|
|
335
372
|
```json
|
|
336
373
|
{
|
|
337
374
|
"action": "remove_worktree",
|
|
375
|
+
"deletedIgnoredPaths": [],
|
|
338
376
|
"handoff": {
|
|
339
377
|
"cwd": null,
|
|
340
378
|
"branch": "feature/worktree-docs",
|
|
@@ -362,7 +400,7 @@ After the pull request **merged on the host**, run from the repository root:
|
|
|
362
400
|
|
|
363
401
|
`remote` defaults to `origin`. Omit `worktreePath` to find the linked worktree holding `sourceBranch`, if any. An explicit path can also select a worktree incorrectly parked on `targetBranch`; unrelated branches, primary/root checkouts, detached, locked, dirty, or foreign worktrees are not removed. Invocation from inside the removal directory (including a nested or symlinked cwd) is refused: **run from the repository root**. Other caller directories in the same repository are supported; every Git command has an explicit `-C` directory, and cleanup runs through the primary checkout without switching any branches.
|
|
364
402
|
|
|
365
|
-
The tool fetches the exact remote target and requires the captured source tip to be its ancestor before cleanup.
|
|
403
|
+
The tool fetches the exact remote target and by default requires the captured source tip to be its ancestor before cleanup. For squash/rebase merges, supply `pullRequestNumber`. BranchMe verifies a closed, merged PR in the same GitHub repository as the configured landing remote, exact head/base branch names, the source SHA, and containment of the reported merge commit in the fetched target. Supplying a PR number requires valid evidence even when ordinary ancestry succeeds. There is no general force escape hatch. This host-merge proof authorizes retiring the original rewritten source history; those original commits may eventually become unreachable. An existing selected source checkout must match the proven PR head exactly; a checkout parked on the target still needs target ancestry. Tracked/staged changes and non-ignored untracked files block removal. **Ignored `.env`, `.pi/`, `node_modules/`, `dist/`, and other ignored residue are deleted with the worktree**; preserve anything needed first. Only their top-level path entries, never contents, appear in `deletedIgnoredPaths`. Source retirement uses the captured expected-HEAD lease and the **remote-tracking target**, not local `HEAD` or a stale local target.
|
|
366
404
|
|
|
367
405
|
Target sync is last, even when worktree removal or branch retirement is refused:
|
|
368
406
|
|
|
@@ -374,7 +412,7 @@ Target sync is last, even when worktree removal or branch retirement is refused:
|
|
|
374
412
|
| `noop` | Local target already equals the captured remote head; no sync mutation. |
|
|
375
413
|
| `not-run` / `failed` | Initial safety/ancestry refusal or sync failure; a successful fast-forward is never inferred from Git's prose. |
|
|
376
414
|
|
|
377
|
-
The structured result contains `repositoryRoot`, `remote`, `targetBranch`, `remoteTargetHead`, `sourceBranch`, `sourceHead`, `ancestry.isAncestor`, `worktree`, `branch`, `targetSync`, and ordered `steps`, plus a one-line summary. Worktree outcomes are `removed`/`absent`/`refused`; branch outcomes are `deleted`/`absent`/`refused`. Full target `before`/`after` IDs are read from the exact local ref in this call. Unknown/not-applicable identities and ancestry are `null`, not invented proofs. A second successful call reports absent cleanup and `noop` sync. An already-missing directory is reported absent without pruning a remaining registration; such occupancy can still block branch deletion.
|
|
415
|
+
The structured result contains `repositoryRoot`, `remote`, `targetBranch`, `remoteTargetHead`, `sourceBranch`, `sourceHead`, `ancestry.isAncestor`, `mergeProof`, `pullRequest`, `worktree`, `branch`, `targetSync`, and ordered `steps`, plus a one-line summary. Worktree outcomes are `removed`/`absent`/`refused`; branch outcomes are `deleted`/`absent`/`refused`. Full target `before`/`after` IDs are read from the exact local ref in this call. Unknown/not-applicable identities and ancestry are `null`, not invented proofs. A second successful call reports absent cleanup and `noop` sync. An already-missing directory is reported absent without pruning a remaining registration; such occupancy can still block branch deletion.
|
|
378
416
|
|
|
379
417
|
`land_branch` never stashes, resets, checks out/switches branches, force-updates, pushes, prunes, or deletes remote/tracking refs. It holds one process-local primary-root mutation queue; it cannot lock other Pi/external Git processes. Inspect each receipt outcome before declaring landing complete. Standalone `remove_worktree` and `retire_branch` retain their original contracts.
|
|
380
418
|
|
|
@@ -425,7 +463,7 @@ BranchMe operates only on the repository where pi is running:
|
|
|
425
463
|
- `pull_request` creates PRs only for the resolved current GitHub repository, requires resolved `headBranch` and `baseBranch` values to be distinct and exist locally, requires the GitHub `headBranch` commit to match the local branch, queues behind in-flight same-repository git mutation windows when possible, and rejects `owner:branch` head refs. Missing PR fields fail unless `BRANCHME_PR_AUTOFILL=true`.
|
|
426
464
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, `pull_request` fails closed.
|
|
427
465
|
|
|
428
|
-
BranchMe intentionally does **not** stage files, create user-authored commits, accept or generate commit messages, force checkout, stash changes, discard changes, force-push, reset, edit files directly, copy ignored/untracked files between worktrees, or delete branches during standalone `remove_worktree`. Rebase-driven rewriting occurs only through explicit `rebase_branch`; a Git-generated standard merge commit is possible
|
|
466
|
+
BranchMe intentionally does **not** stage files, create user-authored commits, accept or generate commit messages, force checkout, stash changes, discard changes, force-push, reset, edit files directly, copy ignored/untracked files between worktrees, or delete branches during standalone `remove_worktree`. Rebase-driven rewriting occurs only through explicit `rebase_branch`; a Git-generated standard merge commit is possible through explicit `integrate_branch` or `update_from_base` for divergent histories; one exact local ref can be deleted only through explicit `retire_branch` or merged-only `land_branch` under the leased boundaries above.
|
|
429
467
|
|
|
430
468
|
---
|
|
431
469
|
|
|
@@ -467,13 +505,13 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
467
505
|
|
|
468
506
|
| Problem | Try |
|
|
469
507
|
| --- | --- |
|
|
470
|
-
| Not a git repository | Start pi from inside a git checkout. |
|
|
508
|
+
| Not a git repository | Start pi from inside a git checkout, or explicitly request `init_repository` for a new project directory. |
|
|
471
509
|
| Detached `HEAD` | Use `change_branch` to switch to an existing local branch, or checkout a branch before `fetch_branch`, `pull_branch`, `rebase_branch`, `create_branch`, or `push_branch`. |
|
|
472
510
|
| Branch already exists | Choose a new local branch name for `create_branch`, or use `change_branch` to switch to it. |
|
|
473
|
-
| Branch does not exist locally |
|
|
511
|
+
| Branch does not exist locally | Use `track_branch` for an existing remote branch; `change_branch` and `create_worktree` existing mode still require local branches. |
|
|
474
512
|
| Worktree destination rejected | Provide an exact absolute path whose immediate parent exists; the destination must not exist or be inside another registered worktree or the repository's common Git directory. Its canonical path and branch identity must also fit the documented limits without credential-like token text or characters that require escaping. |
|
|
475
513
|
| Existing worktree branch is occupied | Choose another existing local branch or remove its other linked checkout after cleaning it; BranchMe does not force multiple checkouts. |
|
|
476
|
-
| Worktree removal rejected | Use `list_worktrees
|
|
514
|
+
| Worktree removal rejected | Use `list_worktrees` and select a non-main/non-current linked worktree. Remove or preserve staged, unstaged, untracked, and unmerged files. Preserve valuable ignored files, then retry with `deleteIgnored: true` if deleting the remaining ignored residue is intended. Locked, detached, prunable/missing, bare, and foreign paths are not removable. |
|
|
477
515
|
| Linked-worktree agent cannot find credentials | Pass credentials through the process environment. BranchMe does not copy repository-root `.env` or other ignored/untracked files. |
|
|
478
516
|
| Dirty worktree before branch switch, pull, rebase, or integration | Commit, stash, or discard changes outside BranchMe before using `change_branch`, `pull_branch`, `rebase_branch`, or a target control worktree for `integrate_branch`. |
|
|
479
517
|
| Fetch, pull, or rebase has no upstream | Configure the current branch upstream outside BranchMe, then retry the tool. |
|
|
@@ -495,7 +533,7 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
495
533
|
| PR branch does not exist locally | Create or fetch/check out the local `headBranch` and `baseBranch` branches first; BranchMe does not use remote-only or cross-repository PR refs. |
|
|
496
534
|
| PR branch is not visible or is stale on GitHub | Run `push_branch`, wait for it to complete, then retry `pull_request`; do not batch `push_branch` and `pull_request` in the same assistant tool call. |
|
|
497
535
|
| Repository mismatch | Make `origin` and `GITHUB_REPOSITORY` refer to the same `owner/repo`. |
|
|
498
|
-
| Need a user-authored commit | Use CommitMe or normal Git commands. BranchMe does not stage files, accept commit messages, or create user-authored commits;
|
|
536
|
+
| Need a user-authored commit | Use CommitMe or normal Git commands. BranchMe does not stage files, accept commit messages, or create user-authored commits; `integrate_branch` and `update_from_base` may let Git create a standard merge commit. |
|
|
499
537
|
| Other extensions interfere | Test with `pi --no-extensions -e .`. |
|
|
500
538
|
|
|
501
539
|
---
|
|
@@ -510,7 +548,7 @@ npm run check:pack
|
|
|
510
548
|
printf '/branchme help\n/quit\n' | pi --no-extensions -e .
|
|
511
549
|
```
|
|
512
550
|
|
|
513
|
-
Validation covers TypeScript typechecking, formatting checks, automatic context collection and prompt injection, mocked GitHub lookup, isolated real-Git worktree, branch-integration, and leased branch-retirement lifecycle tests, package checks, checkout Pi runtime smoke, and package-content verification. The checkout smoke loads BranchMe through Pi, then uses a temporary verifier command to confirm all
|
|
551
|
+
Validation covers TypeScript typechecking, formatting checks, automatic context collection and prompt injection, mocked GitHub lookup, isolated real-Git worktree, branch-integration, and leased branch-retirement lifecycle tests, package checks, checkout Pi runtime smoke, and package-content verification. The checkout smoke loads BranchMe through Pi, then uses a temporary verifier command to confirm all nineteen BranchMe tools are visible through `pi.getAllTools()` with strict schemas and prompt metadata, including `integrate_branch`, `retire_branch`, and targeted `branch_status.ancestry`, with no merge-continuation tool. Runtime smoke inspects retirement and landing registration/schema but never executes either cleanup tool. Smoke-test notes are recorded in [`docs/SMOKE_TEST.md`](docs/SMOKE_TEST.md), and TUI/help captures are stored in [`docs/TUI_CAPTURE.md`](docs/TUI_CAPTURE.md).
|
|
514
552
|
|
|
515
553
|
Refresh TUI captures intentionally with:
|
|
516
554
|
|
package/SECURITY.md
CHANGED
|
@@ -11,33 +11,36 @@ pi install git:https://github.com/senad-d/branchme@<tag>
|
|
|
11
11
|
|
|
12
12
|
## Git behavior
|
|
13
13
|
|
|
14
|
-
BranchMe runs local `git` commands through Pi's extension API with argv-style arguments.
|
|
14
|
+
BranchMe runs local `git` commands through Pi's extension API with argv-style arguments. `init_repository` first canonicalizes its non-repository target directory; other repository mutations resolve the Git root and run from that verified root.
|
|
15
15
|
|
|
16
16
|
Implemented git mutations are limited to:
|
|
17
17
|
|
|
18
|
+
- `init_repository`: `git init --no-template --initial-branch <initialBranch>` in pi's exact canonical current directory after rejecting filesystem root, an existing `.git` entry, reinitialization, and nesting inside another repository. It verifies an in-place non-bare `.git` directory, the requested unborn branch, and absence of a commit.
|
|
18
19
|
- `change_branch`: `git switch <branchName>` after branch-name validation, local `refs/heads/<branchName>` verification, and clean-worktree preflight.
|
|
19
20
|
- `create_branch`: `git switch -c <branchName>` from current `HEAD` after branch-name validation and existing-branch checks.
|
|
21
|
+
- `track_branch`: narrow fetch followed by `git switch --no-overwrite-ignore --track=direct -c <branchName> refs/remotes/<remote>/<remoteBranch>`. Requires a new local branch, clean idle checkout, direct fetched commit ref, and verified final HEAD/upstream.
|
|
22
|
+
- `update_from_base`: narrow fetch followed by the fixed integration merge policy against a captured remote-base commit. Reuses verified outcomes and automatic conflict abort; does not rewrite published history or change upstream. Both new fetch workflows explicitly disable pruning, extra ref mappings, tags, and submodule recursion, and reject symbolic remote-tracking destinations before fetching so Git cannot dereference them into unrelated refs. Base updates compare stored upstream configuration, not whether cached upstream refs happen to resolve.
|
|
20
23
|
- `fetch_branch`: `git fetch --no-tags --no-recurse-submodules <upstreamRemote> <upstreamBranchRef>:<remoteTrackingRef>` after validating the current branch's configured upstream target. The explicit destination is limited to that upstream's remote-tracking ref, so local branches and working-tree files are not changed.
|
|
21
24
|
- `pull_branch`: `git pull --ff-only --no-rebase --no-autostash <upstreamRemote> <upstreamBranchRef>` for the clean current branch after validating its configured upstream target.
|
|
22
25
|
- `rebase_branch`: `git rebase --no-autostash --no-update-refs <upstream>` for the clean current branch after validating its configured upstream target. It rewrites local commits and automatically attempts `git rebase --abort` without the cancelled caller signal if the rebase fails or is killed.
|
|
23
26
|
- `integrate_branch`: after rejecting a non-empty `branch.<targetBranch>.mergeOptions` setting, runs `git -c rerere.enabled=false merge --ff --no-edit --no-autostash --no-rerere-autoupdate --no-overwrite-ignore refs/heads/<sourceBranch>` from the verified clean control worktree, which must already have the distinct existing local `targetBranch` checked out. It uses normal merge semantics: no-op, fast-forward, or a Git-generated standard two-parent merge commit for divergent histories.
|
|
24
27
|
- `push_branch`: `git push <upstreamRemote> HEAD:<upstreamBranchRef>` for the current branch when an upstream exists, or `git push --set-upstream origin <currentBranch>` when no upstream exists.
|
|
25
|
-
- `create_worktree`: `git worktree add -b <branchName> <canonicalPath>
|
|
28
|
+
- `create_worktree`: `git worktree add -b <branchName> <canonicalPath> <capturedCommit>` for a new local branch from current HEAD or optional read-only `baseRef`, or `git worktree add <canonicalPath> <existingLocalBranch>` for an existing unoccupied local branch, after destination and repository-boundary validation.
|
|
26
29
|
- `remove_worktree`: `git worktree remove <verifiedCanonicalPath>` without force, only after fresh repository-membership, safety-state, path, tracked/untracked status, and ignored-entry checks. The local branch is retained and verified at the same commit.
|
|
27
30
|
- `land_branch`: explicit post-host-merge targeted fetch, linked-worktree removal including ignored residue, leased source-local-ref deletion against the fetched remote target, and independent final target sync. Every Git command uses an explicit `-C` directory; unoccupied targets use a non-forced fetch refspec, clean occupied targets use an explicit ff-only pull, and dirty targets are left untouched. No remote mutation, prune, stash, reset, or branch switching is attempted.
|
|
28
31
|
- `retire_branch`: `git update-ref --no-deref -d refs/heads/<branchName> <capturedRetiringHead>` only for one exact unoccupied direct local ref whose commit matches the required full `expectedHead`. The exact local target commit and ancestry are captured first; unmerged retirement requires explicit `force: true` authorization.
|
|
29
32
|
|
|
30
|
-
Before each agent run, BranchMe also runs bounded, read-only Git commands to collect branch/upstream/ahead-behind state, working-tree counts, up to 20 unstaged or untracked path entries, and up to 5 recent commits. The same collector runs when `branch_status` explicitly refreshes context. An explicit optional `branch_status.ancestry` query captures exact local source/target commit IDs and uses `git merge-base --is-ancestor` against those commits; automatic Git context never runs this query and remains unchanged. Collection does not run `fetch`, `switch`, `pull`, `rebase`, `merge`, `push`, `add`, `commit`, or any other mutation, and it never reads diffs or file contents.
|
|
33
|
+
Before each agent run, BranchMe also runs bounded, read-only Git commands to collect branch/upstream/ahead-behind state, working-tree counts, up to 20 unstaged or untracked path entries, and up to 5 recent commits. The same collector runs when `branch_status` explicitly refreshes context. An explicit optional `branch_status.ancestry` query captures exact local or remote-tracking source/target commit IDs and uses `git merge-base --is-ancestor` against those commits; automatic Git context never runs this query and remains unchanged. Collection does not run `fetch`, `switch`, `pull`, `rebase`, `merge`, `push`, `add`, `commit`, or any other mutation, and it never reads diffs or file contents.
|
|
31
34
|
|
|
32
|
-
Branch switching, worktree creation/removal, fast-forward pulls, successful rebases, and branch integration can update or remove filesystem content as normal Git behavior; fetch updates one validated remote-tracking ref without changing local branches or the working tree, and retirement deletes one verified local branch ref. Mutating operations for the same repository are serialized to avoid same-turn races. `integrate_branch` holds one queue window across preflight, merge, cleanup, and final verification; `retire_branch` holds one active-worktree-keyed window across preflight, immediate reinspection, leased deletion, and postcondition verification; `pull_request` uses the queue around PR preflight and creation. This in-memory queue is process-local: it coordinates BranchMe calls using the same active checkout but does not lock a different active worktree, another Pi process, or an external Git process. Integration refs are captured and re-verified. Retirement additionally uses an expected-old-value ref lease and rechecks its captured target and complete worktree occupancy. Unexpected or inconclusive movement produces a bounded uncertain error with manual inspection guidance and no reset-based rollback.
|
|
35
|
+
Repository initialization creates `.git` metadata in the current directory. Branch switching, worktree creation/removal, fast-forward pulls, successful rebases, and branch integration can update or remove filesystem content as normal Git behavior; fetch updates one validated remote-tracking ref without changing local branches or the working tree, and retirement deletes one verified local branch ref. Mutating operations for the same repository are serialized to avoid same-turn races; initialization uses Pi's file-mutation queue for the new `.git` entry and is serialized by its canonical target directory. `integrate_branch` holds one queue window across preflight, merge, cleanup, and final verification; `retire_branch` holds one active-worktree-keyed window across preflight, immediate reinspection, leased deletion, and postcondition verification; `pull_request` uses the queue around PR preflight and creation. This in-memory queue is process-local: it coordinates BranchMe calls using the same active checkout but does not lock a different active worktree, another Pi process, or an external Git process. Integration refs are captured and re-verified. Retirement additionally uses an expected-old-value ref lease and rechecks its captured target and complete worktree occupancy. Unexpected or inconclusive movement produces a bounded uncertain error with manual inspection guidance and no reset-based rollback.
|
|
33
36
|
|
|
34
|
-
BranchMe rejects a dirty control worktree before `change_branch`, `pull_branch`, `rebase_branch`, and `integrate_branch`, and rejects any staged, unstaged, untracked,
|
|
37
|
+
BranchMe rejects a dirty control worktree before `change_branch`, `pull_branch`, `rebase_branch`, and `integrate_branch`, and rejects any staged, unstaged, untracked, or unmerged entry in a linked worktree before standalone `remove_worktree`. Standalone removal protects ignored residue by default and deletes it only with explicit `deleteIgnored: true` authorization. `land_branch` also allows ignored residue to be deleted, but still refuses tracked/staged or non-ignored untracked changes. Retirement does not require an unrelated active worktree to be clean, but every registered worktree record is inspected and any occupancy of the retiring branch blocks deletion. Integration also rejects an existing merge, rebase, cherry-pick, revert, or sequencer state and any non-empty target-branch `mergeOptions` setting that could alter the fixed command policy. After merge or retirement mutation begins, cleanup/postcondition inspection ignores caller cancellation and uses bounded timeouts. A `conflict` result is returned only after exact repository-relative conflict paths are captured, `git merge --abort` succeeds, source and target refs are restored, repository/control-worktree identity is preserved, operation state is cleared, and the control worktree is clean. Failed non-conflict merges remain errors; inconclusive cleanup or verification is never reported as success. After retirement is attempted, contradictory or inconclusive repository, target-ref, retiring-ref, or occupancy state is an uncertain error stating that retirement may have completed and requiring manual inspection before retry.
|
|
35
38
|
|
|
36
|
-
BranchMe does not force checkout/removal, stash, stage files, create user-authored commits, accept commit messages, reset, force-push, or edit files directly. Rebase-driven rewriting occurs only through explicit `rebase_branch`. Explicit `integrate_branch` may let Git create
|
|
39
|
+
BranchMe does not force checkout/removal, stash, stage files, create user-authored commits, accept commit messages, reset, force-push, or edit project files directly. Rebase-driven rewriting occurs only through explicit `rebase_branch`. Explicit `integrate_branch` or `update_from_base` may let Git create a standard merge commit for divergent histories, but BranchMe exposes no strategy, squash, unrelated-history, signing, force, commit, `continue_merge`, or `abort_merge` control. Explicit `retire_branch` and merged-only `land_branch` are the local branch-deletion surfaces. Standalone retirement has no bulk, pattern, inferred-target, remote-delete, remote-tracking-delete, automatic worktree-removal, or rollback-ref control.
|
|
37
40
|
|
|
38
41
|
## Network behavior
|
|
39
42
|
|
|
40
|
-
`fetch_branch`, `pull_branch`, `land_branch`, and `push_branch` contact the configured Git remote through the user's normal Git transport and credentials. They do not use or inject `GITHUB_TOKEN` or `GH_TOKEN`. `rebase_branch`, `integrate_branch`, and `retire_branch` use locally available refs; BranchMe's direct retirement argv never fetches, pulls, pushes, or names a remote or remote-tracking ref for deletion.
|
|
43
|
+
`track_branch`, `update_from_base`, `fetch_branch`, `pull_branch`, `land_branch`, and `push_branch` contact the configured Git remote through the user's normal Git transport and credentials. They do not use or inject `GITHUB_TOKEN` or `GH_TOKEN`. `rebase_branch`, `integrate_branch`, and `retire_branch` use locally available refs; BranchMe's direct retirement argv never fetches, pulls, pushes, or names a remote or remote-tracking ref for deletion.
|
|
41
44
|
|
|
42
45
|
Git extension points are a separate trust boundary. `integrate_branch` preserves repository-configured hooks, custom merge drivers, clean/smudge filters, and signature policy; it does not pass `--no-verify`. Retirement's `git update-ref` can invoke repository-configured `reference-transaction` hooks. Those configurations may execute arbitrary local commands, mutate other state, or contact networks under the user's identity. BranchMe's fixed argv, direct no-network contract, and direct no-remote-delete guarantees cannot constrain hook behavior.
|
|
43
46
|
|
|
@@ -48,28 +51,35 @@ GET https://api.github.com/repos/{owner}/{repo}/pulls?state=open&head={owner}:{
|
|
|
48
51
|
GET https://api.github.com/repos/{owner}/{repo}/branches/{headBranch}
|
|
49
52
|
GET https://api.github.com/repos/{owner}/{repo}/branches/{baseBranch}
|
|
50
53
|
POST https://api.github.com/repos/{owner}/{repo}/pulls
|
|
54
|
+
GET https://api.github.com/repos/{owner}/{repo}/pulls/{number}
|
|
55
|
+
GET https://api.github.com/repos/{owner}/{repo}/pulls?state={open|all}&head={owner}:{branch}&sort=updated&direction=desc&per_page={1|2}
|
|
51
56
|
```
|
|
52
57
|
|
|
58
|
+
Explicit `pull_request_status`, idempotent `pull_request` lookup, and PR-aware `land_branch` use the last two read-only endpoints. Each request has a 10-second transport/body deadline and 64 KiB response limit. Exact PR number, repository, branch identities, and commit IDs are validated. Status does not certify CI checks or review approvals. Idempotent creation preserves existing PR fields and reuses only an exact head-SHA/base match; HTTP 422 races get one read-only recheck.
|
|
59
|
+
|
|
60
|
+
`list_branches` is an explicit read-only local inventory, limited to 200 returned refs, 128 KiB raw ref output, and 4,000 characters of text. Paths and names are display-safe metadata, not executable handoffs. Upstream counts reflect cached refs, not a fresh remote fetch.
|
|
61
|
+
|
|
53
62
|
The first request is an automatic network boundary: it can run before each agent run and whenever `branch_status` explicitly refreshes context. It is a bounded, read-only lookup for one open pull request whose head is the current local branch (`per_page=1`), with a default 4-second timeout and a 64 KiB response-body limit. It is skipped when repository/branch resolution or authentication is unavailable, so BranchMe never makes an unauthenticated fallback request. Timeout, HTTP, network, malformed, and oversized-response failures become a safe unavailable state without exposing response bodies or raw network errors. Git alone is not used or claimed to provide PR metadata.
|
|
54
63
|
|
|
55
64
|
The branch preflight requests have no body. BranchMe uses the resolved `headBranch` preflight response to compare GitHub's branch commit with the local branch commit before creating the PR. The PR request body contains only the resolved title, head branch, base branch, body, and draft flag. By default every field must be supplied explicitly. When `BRANCHME_PR_AUTOFILL=true`, omitted fields may be derived from local branch names and bounded commit subjects before being sent to GitHub. Generated body bullets escape Markdown punctuation so commit subjects remain text rather than active mentions or formatting. All GitHub response reads are bounded.
|
|
56
65
|
|
|
57
66
|
## Repository boundary
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
`init_repository` operates only on pi's exact current directory before a repository exists. Every other BranchMe operation targets the current repository.
|
|
60
69
|
|
|
70
|
+
- `init_repository` accepts only optional `initialBranch` (default `main`), never a path. It rejects existing and nested repositories and exposes no bare, separate-Git-dir, template, shared, remote, commit, README, `.gitignore`, or configuration controls. Ambient Git repository/object/index path and discovery overrides are refused before execution and rechecked inside the mutation queue. Ancestor `.git` entries block initialization even when Git discovery fails; only an explicit Git non-repository diagnostic permits initialization. Unrecognized or localized discovery failures fail closed. Failed postcondition verification reports partial-state inspection guidance and performs no cleanup.
|
|
61
71
|
- The GitHub repository is inferred from local `origin` and/or `GITHUB_REPOSITORY`.
|
|
62
|
-
- Except for `land_branch`'s optional absolute cleanup path, branch and PR tools never accept filesystem paths. They reject `owner`, `repo`, and owner-prefixed `owner:branch` PR refs. Worktree mutations accept only an explicitly approved absolute `worktreePath`, with `branchName` and `branchMode` additionally required for creation.
|
|
72
|
+
- Except for `land_branch`'s optional absolute cleanup path, repository initialization, branch, and PR tools never accept filesystem paths. They reject `owner`, `repo`, and owner-prefixed `owner:branch` PR refs. Worktree mutations accept only an explicitly approved absolute `worktreePath`, with `branchName` and `branchMode` additionally required for creation.
|
|
63
73
|
- `list_worktrees` is read-only and returns a bounded inventory collected from `git worktree list --porcelain -z`; automatic Git context remains focused on the active worktree.
|
|
64
|
-
- `create_worktree`
|
|
65
|
-
- `remove_worktree` accepts
|
|
74
|
+
- `create_worktree` requires `worktreePath`, `branchName`, and `branchMode` (`new` or `existing`), with optional read-only `baseRef` in new mode. New mode uses current `HEAD` or an exact local/remote-tracking ref or full commit; existing mode requires an existing local branch not checked out elsewhere and does not infer remote branches.
|
|
75
|
+
- `remove_worktree` requires `worktreePath`, accepts optional boolean `deleteIgnored`, requires an exact canonical match in a fresh current-repository inventory, removes no branch, and does not accept force.
|
|
66
76
|
- `retire_branch` accepts exactly `branchName`, a full 40- or 64-hex-character `expectedHead`, a distinct exact local `targetBranch`, and boolean `force`. Both names must resolve to direct local refs. Paths, repositories, remotes, remote-only or full refs, refspecs, patterns, arrays, inferred targets, worktree controls, and bulk deletion are rejected.
|
|
67
77
|
- `change_branch` accepts only `branchName` and never creates branches, checks out remote branches, forces, stashes, or discards changes.
|
|
68
|
-
- `fetch_branch` accepts
|
|
78
|
+
- `fetch_branch` accepts optional `branch` and `remote` (remote requires branch), otherwise resolves the current branch's configured upstream. It constructs the source-to-remote-tracking refspec internally and disables tag fetching and submodule recursion; no arbitrary refspec or prune controls are exposed.
|
|
69
79
|
- `pull_branch` accepts no parameters, updates only the clean current branch from its configured upstream, and uses fast-forward-only semantics.
|
|
70
80
|
- `rebase_branch` accepts no parameters, rebases only the clean current branch onto its configured upstream, disables autostash and multi-ref updates, never pushes, and attempts to abort on failure.
|
|
71
81
|
- `integrate_branch` accepts exactly `sourceBranch` and `targetBranch`. Both must be distinct existing local refs in the current repository, and the active clean control worktree must already be on the target. Remote-only refs, full refs, commit IDs, paths, repository/remotes, owner-prefixed refs, and merge controls are rejected. A source branch may be checked out in another dirty linked worktree because only its captured committed ref is read.
|
|
72
|
-
- Targeted `branch_status.ancestry` accepts
|
|
82
|
+
- Targeted `branch_status.ancestry` accepts a nested object containing two exact local branch or remote-tracking names; it is read-only, must run after integration completes, and must not share the same parallel tool batch.
|
|
73
83
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, PR creation and related-PR lookup fail closed.
|
|
74
84
|
- Resolved PR branches are validated as distinct, existing local branch-name refs; identical or missing local branches and cross-repository `head` values are rejected before any GitHub request.
|
|
75
85
|
- PR branch inputs must also be visible on GitHub before the PR is created, and `headBranch` must match the local branch commit; unpublished or stale `headBranch` values fail with guidance to run `push_branch`, wait for it to complete, and retry `pull_request`.
|
|
@@ -78,17 +88,17 @@ BranchMe operates on the current repository only.
|
|
|
78
88
|
|
|
79
89
|
Linked worktree management expands the mutation boundary beyond the active checkout. `create_worktree` may create a directory anywhere the user can write when the explicitly supplied destination passes all checks. BranchMe requires a non-blank absolute path without control characters, requires the immediate parent to exist as a directory, resolves that parent with `realpath`, and rejects any existing destination—including a symlink. It also rejects a destination inside any registered worktree or inside the current repository's common Git directory. Before mutation, the resulting canonical path and local branch must remain exactly identical as JavaScript strings after BranchMe's redaction, control/format escaping, Unicode-safe truncation, and size checks; the path limit is 4,096 characters and the branch limit is 512 characters.
|
|
80
90
|
|
|
81
|
-
`remove_worktree` never passes an unverified user path to Git. It canonicalizes the supplied absolute path, requires an exact match in a fresh inventory belonging to the current repository, applies the same lossless checks to the canonical path and retained branch, and rejects the main worktree, the worktree containing the active Pi session, bare, detached, locked, prunable/missing,
|
|
91
|
+
`remove_worktree` never passes an unverified user path to Git. It canonicalizes the supplied absolute path, requires an exact match in a fresh inventory belonging to the current repository, applies the same lossless checks to the canonical path and retained branch, and rejects the main worktree, the worktree containing the active Pi session, bare, detached, locked, prunable/missing, and dirty worktrees. A separate bounded porcelain scan detects ignored files and directories. They block removal by default without path disclosure; explicit `deleteIgnored: true` authorizes deleting all of them with the worktree and reports only redacted top-level paths, never contents. After force-free removal, BranchMe verifies the worktree is no longer registered and that its local branch remains at the captured `HEAD`.
|
|
82
92
|
|
|
83
93
|
Paths, branch names, lock/prune reasons, and Git output are untrusted metadata. Informational inventory, prose, summaries, and non-identity details are escaped, redacted, and bounded. Successful machine-readable `handoff.cwd` and handoff branch fields instead contain exact verified identities, so BranchMe rejects any identity that would require display transformation before mutation. This also applies to the retained branch returned after removal. Creation failures do not trigger automatic deletion of a possibly created directory or branch; the caller is told to inspect the repository and destination. No force, move, prune, repair, lock, unlock, detached, orphan, or remote-inference worktree operation is implemented.
|
|
84
94
|
|
|
85
|
-
BranchMe does not copy ignored or untracked files, including repository-root `.env` files, into linked worktrees. If a caller creates ignored local files in a linked checkout, those files block standalone `remove_worktree` until they are removed
|
|
95
|
+
BranchMe does not copy ignored or untracked files, including repository-root `.env` files, into linked worktrees. If a caller creates ignored local files in a linked checkout, those files block standalone `remove_worktree` until they are removed, preserved outside the checkout, or explicitly authorized for deletion with `deleteIgnored: true`. An explicit `land_branch` call also authorizes deletion of ignored residue with that worktree. Git documents support for multiple worktrees of a superproject containing submodules as incomplete; BranchMe does not add force-based submodule cleanup.
|
|
86
96
|
|
|
87
97
|
## Post-merge landing boundary
|
|
88
98
|
|
|
89
|
-
`land_branch` is execution-only and accepts required exact local `sourceBranch`/`targetBranch`, optional configured `remote` (default `origin`), and optional
|
|
99
|
+
`land_branch` is execution-only and accepts required exact local `sourceBranch`/`targetBranch`, optional configured `remote` (default `origin`), optional absolute `worktreePath`, and optional positive `pullRequestNumber`. It resolves the primary checkout from the caller's repository and holds its process-local mutation queue for the entire operation. This is not an external-process or cross-worktree lock. Both the actual process cwd and tool-context cwd are checked against the canonical removal path; if either is inside it, landing refuses before fetching and says to run from the repository root.
|
|
90
100
|
|
|
91
|
-
The captured source tip must be an ancestor of the freshly fetched remote-tracking target before any deletion. The primary checkout is never removed. The selected linked checkout must be on the source or target branch, clean of tracked/staged/non-ignored untracked changes, unlocked, attached, and part of the same repository. Its HEAD must also be contained in the captured target. Ignored files and directories, including `.env` and `.pi/`, are intentionally deleted by force-free `git worktree remove`; preserve needed files first. The receipt lists redacted top-level ignored paths, never file contents. The source branch is then retired with the same atomic expected-old-value lease and full occupancy checks as standalone retirement, but ancestry is against the fetched remote-tracking ref. No remote or remote-tracking ref is deleted.
|
|
101
|
+
The remote-tracking fetch destination is checked for symbolic indirection before fetching. The captured source tip must be an ancestor of the freshly fetched remote-tracking target before any deletion, unless explicit `pullRequestNumber` evidence proves an exact merged GitHub PR. PR evidence requires matching resolved repository and landing remote, closed/merged state, exact source branch/SHA and target branch, and containment of the reported merge commit in the fetched target. The receipt preserves actual graph ancestry (which can remain false) separately from `mergeProof: "pull-request"`. This proof authorizes internal non-ancestor local retirement using the existing expected-HEAD lease; no public force option is added. Original pre-squash/rebase commits may eventually become unreachable. The primary checkout is never removed. The selected linked checkout must be on the source or target branch, clean of tracked/staged/non-ignored untracked changes, unlocked, attached, and part of the same repository. Its HEAD must also be contained in the captured target, or be the exact proven merged PR head on the source branch. A checkout parked on the target still requires graph ancestry. Ignored files and directories, including `.env` and `.pi/`, are intentionally deleted by force-free `git worktree remove`; preserve needed files first. The receipt lists redacted top-level ignored paths, never file contents. The source branch is then retired with the same atomic expected-old-value lease and full occupancy checks as standalone retirement, but ancestry is against the fetched remote-tracking ref. No remote or remote-tracking ref is deleted.
|
|
92
102
|
|
|
93
103
|
Target sync always follows attempted cleanup, including cleanup refusals, unless initial cwd/fetch/ancestry checks refuse the whole operation. No branch switching, stash, reset, force update, or prune occurs. Explicit fetch mappings and prune-disabling flags/config prevent Git's configured extra fetch mappings and pruning from expanding the landing scope. Actual local target refs are read before/after sync; errors, dirty skips, and unattempted steps are reported, not described as successful fast-forwards. After possible sync mutation, final ref verification ignores cancellation. Missing branches/worktrees are idempotent; a leftover missing-worktree registration is not pruned and can still block retirement. Repository hooks and external concurrent mutations remain within the existing trust/uncertainty boundary.
|
|
94
104
|
|