@senad-d/branchme 0.1.6 → 0.1.8
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/.env.example +6 -2
- package/CHANGELOG.md +9 -5
- package/README.md +96 -19
- package/SECURITY.md +25 -7
- package/docs/PROJECT_DEFINITION_BRIEF.md +112 -77
- package/docs/SMOKE_TEST.md +21 -11
- package/docs/STRUCTURE.md +22 -17
- package/docs/TUI_CAPTURE.md +53 -22
- package/package.json +11 -7
- package/src/commands/branchme-command.ts +10 -1
- package/src/constants.ts +15 -0
- package/src/git-context.ts +22 -2
- package/src/git.ts +1124 -4
- package/src/github.ts +76 -27
- package/src/tools/branchme-tools.ts +340 -21
- package/src/types.ts +120 -0
- package/src/ui/branchme-panel.ts +14 -3
package/.env.example
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# BranchMe GitHub token template.
|
|
2
|
-
# Copy this file to .env only when you need pull_request support.
|
|
3
|
-
# Keep committed placeholders empty; put real tokens only in your local .env file.
|
|
2
|
+
# Copy this file to .env only when you need pull_request support or PR field autofill.
|
|
3
|
+
# Keep committed token placeholders empty; put real tokens only in your local .env file.
|
|
4
4
|
GITHUB_TOKEN=
|
|
5
5
|
GH_TOKEN=
|
|
6
|
+
|
|
7
|
+
# Optional: allow pull_request to fill omitted branches, title, body, and draft.
|
|
8
|
+
# Explicit values always win. Disabled by default.
|
|
9
|
+
BRANCHME_PR_AUTOFILL=false
|
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.8 - Unreleased
|
|
4
4
|
|
|
5
5
|
- Implemented the `branchme` informational slash command with help aliases.
|
|
6
|
-
- Added strict BranchMe tools: `branch_status`, `change_branch`, `fetch_branch`, `pull_branch`, `rebase_branch`, `create_branch`, `push_branch`, and `
|
|
7
|
-
- 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,
|
|
6
|
+
- Added strict BranchMe tools: `branch_status`, `change_branch`, `fetch_branch`, `pull_branch`, `rebase_branch`, `create_branch`, `push_branch`, `pull_request`, `list_worktrees`, `create_worktree`, and `remove_worktree`.
|
|
7
|
+
- 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, current-branch push/publish, and bounded NUL-delimited worktree discovery.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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, ignored-entry-containing, detached, locked, prunable/missing, bare, and foreign worktrees are rejected.
|
|
10
|
+
- 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.
|
|
8
11
|
- Added GitHub repository resolution, environment-token and local `.env` token fallback handling, REST pull request creation, response validation, and token redaction.
|
|
12
|
+
- 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.
|
|
9
13
|
- Added bounded automatic Git context before each agent run with branch/upstream state, working-tree counts and unstaged paths, authenticated related-open-PR lookup, and recent commits.
|
|
10
14
|
- Expanded `branch_status` into a shared, explicit, read-only context refresh for state that may change during a run.
|
|
11
|
-
- Added unit tests with mocked `pi.exec` and `fetch` for Git context collection and prompt safety, git helpers, GitHub helpers, command behavior, tool schemas, prompt metadata, and extension registration.
|
|
12
|
-
- Updated public documentation for automatic context behavior, authenticated lookup and prompt-insertion security boundaries, package structure, and validation commands.
|
|
15
|
+
- Added unit tests with mocked `pi.exec` and `fetch` for Git context collection and prompt safety, git and worktree helpers, GitHub helpers, command behavior, strict tool schemas, prompt metadata, and extension registration, plus isolated temporary-repository worktree lifecycle integration coverage.
|
|
16
|
+
- Updated public documentation for automatic context behavior, authenticated lookup and prompt-insertion security boundaries, specialized Git-subagent worktree handoff, package structure, and validation commands.
|
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
Current-repository branch and pull request tools for <a href="https://pi.dev">pi</a>.
|
|
14
|
-
<br />Inspect branch state,
|
|
13
|
+
Current-repository branch, worktree, and pull request tools for <a href="https://pi.dev">pi</a>.
|
|
14
|
+
<br />Inspect branch state, manage verified linked worktrees, update 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 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 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 eleven agent-callable tools that refresh state, manage 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,13 +30,14 @@ BranchMe is a Pi extension for safe branch workflow automation. Before each agen
|
|
|
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
|
-
- **
|
|
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.
|
|
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
|
+
- **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.
|
|
35
36
|
- **Commit-safe:** context collection is read-only, and BranchMe never stages files, creates user-authored commits, generates commit messages, force-pushes, creates merge commits, resets, or edits files directly.
|
|
36
|
-
- **Strict tools:** tool schemas reject extra properties such as `force`, `stash`, `discard`, `owner`, `repo`, `path`, or `baseRef
|
|
37
|
-
- **PR-ready:** create GitHub pull requests from existing local branches after verifying the `headBranch` matches GitHub and the base is visible
|
|
37
|
+
- **Strict tools:** tool schemas reject extra properties such as `force`, `stash`, `discard`, `owner`, `repo`, `path`, or `baseRef`; worktree tools accept only their documented fields.
|
|
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.
|
|
38
39
|
|
|
39
|
-
> **Security:** pi packages run with your full system permissions. BranchMe runs local `git` commands, may make an automatic authenticated GitHub request to find a related open pull request, can
|
|
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 make an automatic authenticated GitHub request to find a related open pull request, can update branches and remotes, and can create GitHub pull requests. Read [`SECURITY.md`](SECURITY.md).
|
|
40
41
|
|
|
41
42
|
## Table of Contents
|
|
42
43
|
|
|
@@ -89,9 +90,19 @@ A typical BranchMe flow is:
|
|
|
89
90
|
7. Push the current branch with `push_branch`.
|
|
90
91
|
8. After `push_branch` completes and GitHub can see the branches, create a pull request with `pull_request`.
|
|
91
92
|
|
|
93
|
+
For isolated work, a specialized Git subagent can use the explicit worktree workflow:
|
|
94
|
+
|
|
95
|
+
1. Call `list_worktrees` to inspect the current repository's bounded worktree inventory.
|
|
96
|
+
2. Ask the user to provide or approve an exact absolute destination and call `create_worktree` with `branchMode: "new"` or `"existing"`.
|
|
97
|
+
3. Wait for the result and require `details.handoff.ready === true`.
|
|
98
|
+
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, unmerged, or ignored local files, then explicitly call `remove_worktree` if removal was requested. Removal retains the local branch.
|
|
100
|
+
|
|
101
|
+
BranchMe does not change the active Pi process's cwd, start Pi or other processes, create sessions, copy `.env` or other ignored/untracked files, or delete branches. For credentials needed by agents in linked worktrees, prefer process-level environment variables rather than copying repository-root secrets.
|
|
102
|
+
|
|
92
103
|
To refresh the current branch's configured remote-tracking ref without changing the local branch or working tree, use `fetch_branch`. To reconcile the clean current branch by rewriting its local commits, run `fetch_branch`, wait for it to complete, and then run `rebase_branch`. Both tools require a configured upstream; `rebase_branch` automatically attempts `git rebase --abort` if rebasing fails.
|
|
93
104
|
|
|
94
|
-
BranchMe is tool-based. The slash command is informational only and never changes or updates branches, fetches, rebases, pushes, commits, stages, edits files, or opens pull requests.
|
|
105
|
+
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.
|
|
95
106
|
|
|
96
107
|
---
|
|
97
108
|
|
|
@@ -153,11 +164,12 @@ export GH_TOKEN=ghp_...
|
|
|
153
164
|
pi
|
|
154
165
|
```
|
|
155
166
|
|
|
156
|
-
Or copy `.env.example` to `.env` in the repository root
|
|
167
|
+
Or copy `.env.example` to `.env` in the repository root, fill in one token value, and optionally enable PR field autofill:
|
|
157
168
|
|
|
158
169
|
```bash
|
|
159
170
|
cp .env.example .env
|
|
160
171
|
$EDITOR .env
|
|
172
|
+
# Set BRANCHME_PR_AUTOFILL=true in .env if desired.
|
|
161
173
|
pi
|
|
162
174
|
```
|
|
163
175
|
|
|
@@ -169,15 +181,26 @@ Run `pull_request` only after `push_branch` has completed; `pull_request` prefli
|
|
|
169
181
|
|
|
170
182
|
## Configuration
|
|
171
183
|
|
|
172
|
-
BranchMe has no project config file. It reads process environment variables
|
|
184
|
+
BranchMe has no separate project config file. It reads process environment variables and supported keys from a local `.env` file in the verified git root. Token lookup checks `process.env.GITHUB_TOKEN`, then `process.env.GH_TOKEN`; if neither is set, BranchMe checks the matching `.env` keys. Pull request field autofill checks `BRANCHME_PR_AUTOFILL` in the process environment first, then `.env`, and defaults to disabled.
|
|
173
185
|
|
|
174
186
|
| Variable | Meaning |
|
|
175
187
|
| --- | --- |
|
|
176
188
|
| `GITHUB_TOKEN` | Preferred token for automatic related-PR lookup and `pull_request`; process environment first, then local `.env` fallback. |
|
|
177
189
|
| `GH_TOKEN` | Fallback token for automatic related-PR lookup and `pull_request`; process environment first, then local `.env` fallback. |
|
|
190
|
+
| `BRANCHME_PR_AUTOFILL=true` | Allow `pull_request` to fill omitted PR fields. Accepts `true`/`false`, `1`/`0`, `yes`/`no`, or `on`/`off`; disabled by default. |
|
|
178
191
|
| `GITHUB_REPOSITORY=owner/repo` | Optional CI fallback and boundary check for the current GitHub repository; process environment only. |
|
|
179
192
|
|
|
180
|
-
BranchMe reads only `GITHUB_TOKEN` and `
|
|
193
|
+
BranchMe reads only `GITHUB_TOKEN`, `GH_TOKEN`, and `BRANCHME_PR_AUTOFILL` from a small regular `.env` file; it rejects directories, symlinks, special files, and oversized files. BranchMe does not import other `.env` keys, read shell profiles, GitHub CLI credentials, or local credential stores. Token values are redacted from automatic context, errors, tool content, and tool details.
|
|
194
|
+
|
|
195
|
+
With autofill enabled, omitted fields are resolved as follows:
|
|
196
|
+
|
|
197
|
+
- `headBranch`: current local branch.
|
|
198
|
+
- `baseBranch`: the branch named by `origin/HEAD` when it exists locally, falling back to an existing local `main`, `master`, `trunk`, or `develop` branch.
|
|
199
|
+
- `title`: first commit subject in `baseBranch..headBranch`, falling back to a title derived from the head branch name.
|
|
200
|
+
- `body`: a bounded Markdown summary of commit subjects in `baseBranch..headBranch`.
|
|
201
|
+
- `draft`: `false`.
|
|
202
|
+
|
|
203
|
+
Explicit tool arguments always take precedence. Autofill does not create a PR by itself: the user must still ask the agent to create one.
|
|
181
204
|
|
|
182
205
|
If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, `pull_request` fails closed.
|
|
183
206
|
|
|
@@ -201,15 +224,18 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
201
224
|
| Tool | Schema | Behavior |
|
|
202
225
|
| --- | --- | --- |
|
|
203
226
|
| `branch_status` | `{}` | Explicitly refreshes the same bounded context used at agent start: repo root in structured details, branch/detached state, upstream and ahead/behind counts, working-tree counts and unstaged/untracked paths, related open PR, and recent commits. It is read-only. |
|
|
227
|
+
| `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. |
|
|
228
|
+
| `create_worktree` | `{ "worktreePath": string, "branchName": string, "branchMode": "new" \| "existing" }` | Creates and verifies a linked worktree at an explicitly approved absolute path. `new` creates a local branch from current `HEAD`; `existing` requires an existing local branch not checked out elsewhere. It returns a ready handoff with the exact canonical absolute cwd and local branch identity. |
|
|
229
|
+
| `remove_worktree` | `{ "worktreePath": string }` | Force-free removal of an explicitly selected, verified clean linked worktree. It rejects the main/current, detached, locked, prunable/missing, dirty, ignored-file-containing, or foreign worktree and verifies that the local branch remains at the same commit. |
|
|
204
230
|
| `change_branch` | `{ "branchName": string }` | Validates `branchName`, requires `refs/heads/<branchName>` to exist locally, rejects dirty worktrees, and runs `git switch <branchName>`. |
|
|
205
231
|
| `fetch_branch` | `{}` | 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>`; only that tracking ref is refreshed without changing local branches or working-tree files. |
|
|
206
232
|
| `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. |
|
|
207
233
|
| `rebase_branch` | `{}` | Requires a clean current branch with a configured upstream and runs `git rebase --no-autostash --no-update-refs <upstream>`; it rewrites local commits and automatically attempts `git rebase --abort` on failure. |
|
|
208
234
|
| `create_branch` | `{ "branchName": string }` | Validates `branchName`, rejects existing local branches, and runs `git switch -c <branchName>` from current `HEAD`. |
|
|
209
235
|
| `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. |
|
|
210
|
-
| `pull_request` | `{ "headBranch"
|
|
236
|
+
| `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`. |
|
|
211
237
|
|
|
212
|
-
All schemas reject additional properties. `change_branch` never accepts `baseRef`, `force`, `stash`, `discard`, `create`, `owner`, `repo`, or path inputs. `fetch_branch`, `pull_branch`, and `rebase_branch` have strict empty schemas and never accept a branch, remote, refspec, force, autostash, or arbitrary rebase target. `pull_request` never accepts `owner`, `repo`, or owner-prefixed branch refs; BranchMe resolves the repository from local `origin` and/or matching `GITHUB_REPOSITORY`.
|
|
238
|
+
All schemas reject additional properties. `change_branch` never accepts `baseRef`, `force`, `stash`, `discard`, `create`, `owner`, `repo`, or path inputs. `fetch_branch`, `pull_branch`, and `rebase_branch` have strict empty schemas and never accept a branch, remote, refspec, force, autostash, or arbitrary rebase target. `create_worktree` requires exactly `worktreePath`, `branchName`, and `branchMode`; `remove_worktree` requires exactly `worktreePath`. No worktree tool accepts force, move, prune, repair, lock, unlock, detached, orphan, remote, refspec, or arbitrary start-point controls. `pull_request` never accepts `owner`, `repo`, or owner-prefixed branch refs; BranchMe resolves the repository from local `origin` and/or matching `GITHUB_REPOSITORY`.
|
|
213
239
|
|
|
214
240
|
---
|
|
215
241
|
|
|
@@ -223,6 +249,7 @@ Before each agent run, BranchMe appends an **Automatic Git Context** snapshot to
|
|
|
223
249
|
- working-tree state and staged, unstaged, and untracked counts;
|
|
224
250
|
- up to 20 unstaged or untracked change entries with Git status, path, and original path for renames/copies;
|
|
225
251
|
- related open PR status and, when found, its number, title, repository, head/base branches, URL, state, and draft flag;
|
|
252
|
+
- whether pull request field autofill is enabled;
|
|
226
253
|
- up to 5 recent commits with short hash, date, and subject.
|
|
227
254
|
|
|
228
255
|
Collection defaults are a 5-second timeout per local Git command, a 4-second related-PR lookup timeout, at most 512 characters per metadata value, and at most 4,000 characters for the rendered snapshot. GitHub response bodies are limited to 64 KiB. The formatter can further shorten values or omit entries to stay within the total limit.
|
|
@@ -242,8 +269,47 @@ Fetch the current branch upstream with fetch_branch, wait for it to complete, th
|
|
|
242
269
|
Create branch feature/docs-refresh from the updated current HEAD with create_branch.
|
|
243
270
|
Push the current branch with push_branch.
|
|
244
271
|
After push_branch completes, create a draft pull request from feature/docs-refresh to main titled "Refresh docs" with this body: "...".
|
|
272
|
+
If pull request field autofill is enabled, after push_branch completes create a pull request and fill any details I did not provide.
|
|
245
273
|
```
|
|
246
274
|
|
|
275
|
+
### Linked worktree verification and handoff
|
|
276
|
+
|
|
277
|
+
`create_worktree` requires a non-blank absolute path with no control characters. Its immediate parent must already be a directory. BranchMe resolves that parent to build a canonical destination, rejects any existing file, directory, or symlink there, and rejects destinations inside a registered worktree or the repository's common Git directory. Before mutation, the canonical path and local branch must be returnable without redaction, escaping, Unicode alteration, or truncation: canonical paths are limited to 4,096 characters and branch identities to 512 characters, and credential-like token text is rejected. A dirty source worktree is allowed because creation does not switch or overwrite it.
|
|
278
|
+
|
|
279
|
+
For `branchMode: "new"`, BranchMe creates the requested local branch from the current `HEAD` only. For `branchMode: "existing"`, it uses only an existing local branch that is not checked out in another worktree; it never infers a local branch from a remote. After Git succeeds, BranchMe re-lists worktrees and verifies the canonical path, local branch, `HEAD`, and clean checkout. A representative structured result subset is:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"action": "create_worktree",
|
|
284
|
+
"handoff": {
|
|
285
|
+
"cwd": "/absolute/path/to/branchme-feature",
|
|
286
|
+
"branch": "feature/worktree-docs",
|
|
287
|
+
"head": "<full-commit-id>",
|
|
288
|
+
"ready": true,
|
|
289
|
+
"summary": "Worktree ready at /absolute/path/to/branchme-feature on branch feature/worktree-docs at <full-commit-id>."
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
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.
|
|
295
|
+
|
|
296
|
+
`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, unmerged, or ignored entries. The canonical path and retained branch must pass the same pre-mutation lossless-identity checks used for creation. The ignored-entry preflight is bounded and does not return ignored paths. 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:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"action": "remove_worktree",
|
|
301
|
+
"handoff": {
|
|
302
|
+
"cwd": null,
|
|
303
|
+
"branch": "feature/worktree-docs",
|
|
304
|
+
"head": "<full-commit-id>",
|
|
305
|
+
"ready": false,
|
|
306
|
+
"summary": "Worktree directory /absolute/path/to/branchme-feature was removed; local branch feature/worktree-docs was retained at <full-commit-id>."
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Ignored and untracked files—including a repository-root `.env`—are not copied into a new linked worktree. Prefer credentials inherited through the new agent process environment. Git documents support for multiple worktrees of a superproject containing submodules as incomplete; BranchMe adds no force-based submodule cleanup. There is no force, move, prune, repair, lock, unlock, detached, orphan, or remote-inference worktree behavior.
|
|
312
|
+
|
|
247
313
|
BranchMe operates only on the repository where pi is running:
|
|
248
314
|
|
|
249
315
|
- Automatic collection and `branch_status` run bounded, read-only Git commands from the verified git root.
|
|
@@ -253,11 +319,14 @@ BranchMe operates only on the repository where pi is running:
|
|
|
253
319
|
- `pull_branch` requires a clean worktree and configured upstream, updates only the current branch with `git pull --ff-only --no-rebase --no-autostash`, and has no branch, remote, force, or rebase input.
|
|
254
320
|
- `rebase_branch` requires a clean worktree and configured upstream, rewrites only the current branch onto the locally available upstream with autostash and multi-ref updates disabled, and automatically attempts to abort on failure.
|
|
255
321
|
- `create_branch` creates from the current `HEAD` only and has no `baseRef` input.
|
|
322
|
+
- `list_worktrees` is an explicit, read-only repository inventory; it is intentionally absent from automatic active-worktree context.
|
|
323
|
+
- `create_worktree` may create a linked checkout outside the active checkout only after canonical path and current-repository boundary checks; dependent worktree calls must wait for its verified handoff.
|
|
324
|
+
- `remove_worktree` passes Git only a freshly verified canonical linked-worktree path, never uses force, and retains the branch.
|
|
256
325
|
- `push_branch` pushes only the current branch, uses no bare upstream `git push`, and has no `branchName` input.
|
|
257
|
-
- `pull_request` creates PRs only for the resolved current GitHub repository, requires `headBranch` and `baseBranch` to 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.
|
|
326
|
+
- `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`.
|
|
258
327
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, `pull_request` fails closed.
|
|
259
328
|
|
|
260
|
-
BranchMe intentionally does **not** stage files, create user-authored commits, force checkout, stash changes, discard changes, force-push, create merge commits, edit files directly, or generate commit messages. Rebase-driven commit rewriting occurs only through explicit `rebase_branch` calls.
|
|
329
|
+
BranchMe intentionally does **not** stage files, create user-authored commits, force checkout, stash changes, discard changes, force-push, create merge commits, edit files directly, copy ignored/untracked files between worktrees, delete branches during worktree removal, or generate commit messages. Rebase-driven commit rewriting occurs only through explicit `rebase_branch` calls.
|
|
261
330
|
|
|
262
331
|
---
|
|
263
332
|
|
|
@@ -302,7 +371,11 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
302
371
|
| Not a git repository | Start pi from inside a git checkout. |
|
|
303
372
|
| 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`. |
|
|
304
373
|
| Branch already exists | Choose a new local branch name for `create_branch`, or use `change_branch` to switch to it. |
|
|
305
|
-
| Branch does not exist locally | Create a local branch first; `change_branch`
|
|
374
|
+
| Branch does not exist locally | Create a local branch first; `change_branch` and `create_worktree` existing mode do not infer local branches from remote branches. |
|
|
375
|
+
| 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. |
|
|
376
|
+
| 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. |
|
|
377
|
+
| Worktree removal rejected | Use `list_worktrees`, select a non-main/non-current linked worktree, and remove or preserve staged, unstaged, untracked, unmerged, and ignored files outside the checkout. Locked, detached, prunable/missing, bare, and foreign paths are not removable. |
|
|
378
|
+
| Linked-worktree agent cannot find credentials | Pass credentials through the process environment. BranchMe does not copy repository-root `.env` or other ignored/untracked files. |
|
|
306
379
|
| Dirty worktree before branch switch, pull, or rebase | Commit, stash, or discard changes outside BranchMe before using `change_branch`, `pull_branch`, or `rebase_branch`. |
|
|
307
380
|
| Fetch, pull, or rebase has no upstream | Configure the current branch upstream outside BranchMe, then retry the tool. |
|
|
308
381
|
| Pull is not a fast-forward | Run `fetch_branch`, wait for it to complete, then explicitly run `rebase_branch` if rewriting local commits is intended; otherwise reconcile outside BranchMe. |
|
|
@@ -310,6 +383,8 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
310
383
|
| Push fails | Confirm the current branch is correct and your normal Git remote credentials can push. |
|
|
311
384
|
| Related PR is unavailable | Set `GITHUB_TOKEN` or `GH_TOKEN` before starting pi if related-PR context is wanted. Without a token, BranchMe keeps local context and intentionally makes no unauthenticated GitHub request. |
|
|
312
385
|
| PR auth fails | Set `GITHUB_TOKEN` or `GH_TOKEN` before starting pi, or copy `.env.example` to `.env` and fill in one token. |
|
|
386
|
+
| Missing PR fields | Provide all five fields, or set `BRANCHME_PR_AUTOFILL=true` in the process environment or repository `.env`. |
|
|
387
|
+
| Autofill cannot infer the base | Ensure `origin/HEAD` names an existing local branch, keep a local `main`, `master`, `trunk`, or `develop` branch, or provide `baseBranch` explicitly. |
|
|
313
388
|
| 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. |
|
|
314
389
|
| 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. |
|
|
315
390
|
| Repository mismatch | Make `origin` and `GITHUB_REPOSITORY` refer to the same `owner/repo`. |
|
|
@@ -328,7 +403,7 @@ npm run check:pack
|
|
|
328
403
|
printf '/branchme help\n/quit\n' | pi --no-extensions -e .
|
|
329
404
|
```
|
|
330
405
|
|
|
331
|
-
Validation covers TypeScript typechecking, formatting checks, automatic context collection and prompt injection, mocked GitHub lookup,
|
|
406
|
+
Validation covers TypeScript typechecking, formatting checks, automatic context collection and prompt injection, mocked GitHub lookup, isolated real-Git worktree 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 eleven BranchMe tools are visible through `pi.getAllTools()` with strict schemas and prompt metadata. 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).
|
|
332
407
|
|
|
333
408
|
Refresh TUI captures intentionally with:
|
|
334
409
|
|
|
@@ -375,11 +450,13 @@ BranchMe publishes to npm as `@senad-d/branchme`. You need an npm account with p
|
|
|
375
450
|
```bash
|
|
376
451
|
npm login
|
|
377
452
|
npm whoami
|
|
378
|
-
npm run release:check # optional preflight;
|
|
453
|
+
npm run release:check # optional preflight; every publish path runs this gate
|
|
379
454
|
node scripts/publish-npm.mjs
|
|
380
455
|
```
|
|
381
456
|
|
|
382
|
-
|
|
457
|
+
`npm run release:check` is the canonical release gate: it runs checkout validation and then installs and loads the packed npm artifact in isolation. Both the local publish script and the GitHub `Publish to npm` workflow run this gate before npm publication; a failure prevents publication and the workflow's Git tag creation.
|
|
458
|
+
|
|
459
|
+
The publish script requires a clean working tree, asks for the version number, runs `npm run release:check`, runs `npm version <version>` to update `package.json` and `package-lock.json`, creates the `v<version>` git tag, publishes with `npm publish --access public`, and then offers to push the release commit and tag.
|
|
383
460
|
|
|
384
461
|
Run it only from a clean working tree after updating `CHANGELOG.md`.
|
|
385
462
|
|
package/SECURITY.md
CHANGED
|
@@ -21,10 +21,12 @@ Implemented git mutations are limited to:
|
|
|
21
21
|
- `pull_branch`: `git pull --ff-only --no-rebase --no-autostash <upstreamRemote> <upstreamBranchRef>` for the clean current branch after validating its configured upstream target.
|
|
22
22
|
- `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
23
|
- `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.
|
|
24
|
+
- `create_worktree`: `git worktree add -b <branchName> <canonicalPath> HEAD` for a new local branch, or `git worktree add <canonicalPath> <existingLocalBranch>` for an existing unoccupied local branch, after destination and repository-boundary validation.
|
|
25
|
+
- `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.
|
|
24
26
|
|
|
25
27
|
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. Collection does not run `fetch`, `switch`, `pull`, `rebase`, `push`, `add`, `commit`, or any other mutation, and it never reads diffs or file contents.
|
|
26
28
|
|
|
27
|
-
Branch switching, fast-forward pulls, and successful rebases can update
|
|
29
|
+
Branch switching, worktree creation/removal, fast-forward pulls, and successful rebases 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. Mutating operations for the same repository are serialized to avoid same-turn races. `pull_request` also uses the same repository queue around PR preflight and creation so it can wait behind an already-started same-repository mutation. BranchMe rejects dirty worktrees before `change_branch`, `pull_branch`, and `rebase_branch`, and rejects any staged, unstaged, untracked, unmerged, or ignored entry in a linked worktree before removal. It does not force checkout/removal, stash, stage files, create user-authored commits, reset, force-push, create merge commits, or edit files directly. Rebase-driven commit rewriting occurs only through an explicit `rebase_branch` call.
|
|
28
30
|
|
|
29
31
|
## Network behavior
|
|
30
32
|
|
|
@@ -41,22 +43,35 @@ POST https://api.github.com/repos/{owner}/{repo}/pulls
|
|
|
41
43
|
|
|
42
44
|
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.
|
|
43
45
|
|
|
44
|
-
The branch preflight requests have no body. BranchMe uses the `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
|
|
46
|
+
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.
|
|
45
47
|
|
|
46
48
|
## Repository boundary
|
|
47
49
|
|
|
48
50
|
BranchMe operates on the current repository only.
|
|
49
51
|
|
|
50
52
|
- The GitHub repository is inferred from local `origin` and/or `GITHUB_REPOSITORY`.
|
|
51
|
-
-
|
|
53
|
+
- Branch and PR tools never accept filesystem paths, `owner`, `repo`, or owner-prefixed `owner:branch` PR refs. Worktree mutations accept only an explicitly approved absolute `worktreePath`, with `branchName` and `branchMode` additionally required for creation.
|
|
54
|
+
- `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.
|
|
55
|
+
- `create_worktree` accepts exactly `worktreePath`, `branchName`, and `branchMode` (`new` or `existing`). New mode uses current `HEAD` only; existing mode requires an existing local branch not checked out elsewhere and does not infer remote branches.
|
|
56
|
+
- `remove_worktree` accepts exactly `worktreePath`, requires an exact canonical match in a fresh current-repository inventory, removes no branch, and does not accept force.
|
|
52
57
|
- `change_branch` accepts only `branchName` and never creates branches, checks out remote branches, forces, stashes, or discards changes.
|
|
53
58
|
- `fetch_branch` accepts no parameters, resolves the current branch's configured upstream remote and branch, constructs a source-to-remote-tracking refspec internally, disables tag fetching and submodule recursion, and does not prune or accept arbitrary refspecs.
|
|
54
59
|
- `pull_branch` accepts no parameters, updates only the clean current branch from its configured upstream, and uses fast-forward-only semantics.
|
|
55
60
|
- `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.
|
|
56
61
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, PR creation and related-PR lookup fail closed.
|
|
57
|
-
- PR
|
|
62
|
+
- 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.
|
|
58
63
|
- 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`.
|
|
59
64
|
|
|
65
|
+
## Worktree filesystem boundary
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
`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, dirty, and ignored-entry-containing worktrees. A separate bounded porcelain scan detects ignored files and directories without exposing their paths or contents. After force-free removal, BranchMe verifies the worktree is no longer registered and that its local branch remains at the captured `HEAD`.
|
|
70
|
+
|
|
71
|
+
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.
|
|
72
|
+
|
|
73
|
+
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 removal until they are removed or preserved outside the checkout. Git documents support for multiple worktrees of a superproject containing submodules as incomplete; BranchMe does not add force-based submodule cleanup.
|
|
74
|
+
|
|
60
75
|
## Credentials
|
|
61
76
|
|
|
62
77
|
Git fetch, pull, and push authentication is handled by the user's configured Git credential and transport setup. BranchMe never passes GitHub API tokens to Git commands.
|
|
@@ -66,7 +81,9 @@ Git fetch, pull, and push authentication is handled by the user's configured Git
|
|
|
66
81
|
- `GITHUB_TOKEN` (preferred)
|
|
67
82
|
- `GH_TOKEN` (fallback)
|
|
68
83
|
|
|
69
|
-
|
|
84
|
+
BranchMe also reads the non-secret `BRANCHME_PR_AUTOFILL` setting from the process environment or verified-root `.env`; all other `.env` keys are ignored. The `.env` reader uses async file I/O, requires a small regular file, and rejects directories, symlinks, special files, and oversized files. BranchMe does not read shell profiles, GitHub CLI credentials, or local credential stores. Token values are redacted from thrown errors, automatic context, generated PR text, tool content, and tool details.
|
|
85
|
+
|
|
86
|
+
A newly created linked worktree does not receive the source checkout's `.env` or other ignored/untracked files. Start the separate agent with required credentials in its process environment rather than copying secrets into the linked checkout.
|
|
70
87
|
|
|
71
88
|
## System prompt boundary
|
|
72
89
|
|
|
@@ -76,7 +93,7 @@ The automatic snapshot can become stale after a Git or filesystem mutation durin
|
|
|
76
93
|
|
|
77
94
|
## Telemetry
|
|
78
95
|
|
|
79
|
-
BranchMe does not collect telemetry. Related-PR lookup sends only the resolved repository owner/name and current branch in the authenticated GitHub API URL. PR creation sends only the
|
|
96
|
+
BranchMe does not collect telemetry. Related-PR lookup sends only the resolved repository owner/name and current branch in the authenticated GitHub API URL. PR creation sends only the resolved GitHub pull request fields described above. When autofill supplies title or body, those fields can contain bounded, redacted local commit subjects. BranchMe does not send diff contents, filenames, or local file contents to GitHub during context collection.
|
|
80
97
|
|
|
81
98
|
## Reporting vulnerabilities
|
|
82
99
|
|
|
@@ -93,6 +110,7 @@ Do not open public issues for security-sensitive reports that include exploit de
|
|
|
93
110
|
- Do not commit secrets, tokens, local `.env`, local `.pi/` state, or generated artifacts.
|
|
94
111
|
- Keep tool schemas strict and reject unsupported fields.
|
|
95
112
|
- Keep all git calls argv-style through `pi.exec("git", args)`.
|
|
96
|
-
-
|
|
113
|
+
- Treat worktree paths as a filesystem security boundary; canonicalize them, verify current-repository membership before removal, and never add force cleanup.
|
|
114
|
+
- Mock `pi.exec` and `fetch` in unit tests; use only temporary local repositories and directories for real-Git integration tests, and do not touch real remotes.
|
|
97
115
|
- Keep package contents minimal with `npm run check:pack`.
|
|
98
116
|
- Use isolated smoke tests with `pi --no-extensions -e .`.
|