@senad-d/branchme 0.3.15 → 0.3.17
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 +15 -0
- package/README.md +131 -30
- package/SECURITY.md +45 -9
- package/docs/PROJECT_DEFINITION_BRIEF.md +26 -7
- package/docs/SHELL_FREE_WORKFLOWS.md +74 -0
- package/docs/SMOKE_TEST.md +16 -7
- package/docs/STRUCTURE.md +48 -8
- package/docs/TUI_CAPTURE.md +17 -3
- package/package.json +9 -6
- package/src/blocker-guidance.ts +20 -5
- package/src/commands/branchme-command.ts +17 -3
- package/src/commit-draft.ts +461 -0
- package/src/commit-prompt.ts +409 -0
- package/src/commit-workflow.ts +233 -0
- package/src/constants.ts +138 -0
- package/src/git-commit-context.ts +783 -0
- package/src/git-commit.ts +317 -0
- package/src/git-conflicts.ts +77 -0
- package/src/git-diff.ts +274 -0
- package/src/git-history.ts +112 -0
- package/src/git-index.ts +119 -0
- package/src/git-read.ts +100 -0
- package/src/git-rebase.ts +42 -0
- package/src/git-remotes.ts +129 -0
- package/src/git-restore.ts +53 -0
- package/src/git-revert.ts +77 -0
- package/src/git-status.ts +346 -0
- package/src/git.ts +11 -2
- package/src/github-api.ts +39 -0
- package/src/github-checks.ts +88 -0
- package/src/github-lifecycle.ts +106 -0
- package/src/tools/branchme-tools.ts +159 -126
- package/src/tools/commit-tools.ts +240 -0
- package/src/tools/github-lifecycle-tools.ts +29 -0
- package/src/tools/inspection-tools.ts +39 -0
- package/src/tools/mutation-tools.ts +58 -0
- package/src/tools/workflow-tools.ts +32 -49
- package/src/tools/worktree-routing.ts +34 -0
- package/src/truncation.ts +119 -0
- package/src/types.ts +273 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.17 - Unreleased
|
|
4
|
+
|
|
5
|
+
- `revert_changes` refuses only ignored files at the paths the reverted commit touches, which Git could overwrite. Before, any ignored file in the checkout (`.env`, `.pi/`, `node_modules`) refused the revert, and in a repository with installed dependencies the whole-checkout listing passed the 4 MiB read cap first ("Local Git inspection exceeded the 4 MiB safety limit"), so the tool could not run in a working project.
|
|
6
|
+
|
|
7
|
+
## 0.3.16 - Unreleased
|
|
8
|
+
|
|
9
|
+
- Added `commit_changes`, ported from CommitMe with the same behavior: `action: "gather"` returns read-only bounded commit context; `action: "commit"` commits an explicit subject or drafts one with the active model; `paths`, `steeringPrompt`, `confirm`, and unsafe-file approval work as before. The tool uses BranchMe's Git runner, lock retry, redacted Git errors, 30-second mutation timeout, worktree routing, tool mutation coordinator, and blocker guidance. Commits now stage and commit from the repository root, fixing CommitMe's failure when Pi runs in a subdirectory. The `/commitme` command is not included. Uninstall `@senad-d/commitme` to avoid two commit tools.
|
|
10
|
+
- Resolved Sonar findings in Git inspection, guarded index/restoration/revert operations, remote configuration, and GitHub PR workflows: extracted focused helpers, parallelized independent bounded reads, retained bounded filesystem concurrency, made alphabetical sorting explicit, and simplified validation and error formatting. Preserved strict rejection of null diff modes and added mixed-case remote sorting regression coverage.
|
|
11
|
+
|
|
3
12
|
## 0.3.15 - Unreleased
|
|
4
13
|
|
|
14
|
+
- Simplified LLM-facing descriptions, snippets, and guidelines without renaming tools or changing schemas or execution. Descriptions omit repeated names and parameter manuals; schemas explain arguments, with optional rules for essential safety and sequencing. Prose shrank from 4,170 to 1,287 words (about 69%); this is a text measurement, not a model-token count. Added scope/safety and prose-budget regressions, removed mandatory name repetition and nonempty-guideline tests, and verified single-prefix rendering in real Pi smoke.
|
|
15
|
+
- Removed fixed tool-count assertions, prose-total tests, and duplicate name-only inventories in favor of canonical names and exact registration/documentation/schema coverage. Agents never need to estimate an output length; `git_diff({})` uses shared defaults and reports comparison, scope, bounded filter previews, and completeness before the diff body. Added actionable local errors, neutral Git-timeout guidance, corrected guarded-switch metadata for `land_branch`, and a real Pi model-loop smoke for default diff inspection.
|
|
16
|
+
- Completed the shell-free workflow expansion: bounded `git_log`/`git_show`, `git_conflicts` index-stage inspection, consistent same-repository worktree routing and cross-worktree tool mutation coordination, explicit branch/rebase bases, `list_remotes`/`add_remote`/`set_upstream`, guarded `stage_changes`/`unstage_changes`, previewed `restore_paths`, and verified no-commit `revert_changes`. Ordinary commits and CommitMe remain independent. Guarded file/index operations use fresh expectations and conservative unsupported-index boundaries; explicit-base rebases/reverts protect ignored local-only residue by refusing it.
|
|
17
|
+
- Added `pull_request_checks`, explicit PR metadata editing, and guarded host `merge_pull_request`: complete state distinctions, expected-head leases, enabled merge methods, host-readiness checks, read-only reconciliation of uncertain writes, and no bypass/auto-cleanup/remote deletion. Expanded isolated Git/GitHub/schema/runtime tests and documented all safety boundaries in `docs/SHELL_FREE_WORKFLOWS.md`.
|
|
18
|
+
- Added independent read-only `git_status` for complete paginated staged/unstaged/untracked/conflicted/renamed/deleted file discovery, literal filters, verified worktree targeting, current operations, logical index and snapshot expectations, and optional filtered-file content fingerprints without exposing contents. Stale continuation and collection-time changes fail; unsafe displayed paths are explicitly non-reusable. Automatic context and CommitMe remain unchanged.
|
|
19
|
+
- Added read-only `git_diff`: unstaged/staged changes, captured ref comparisons with optional unique merge base, literal root-relative filters, linked-worktree targeting, and patch/stat/name-status output. Redacted/control-escaped display and details are capped at 1,000 lines / 32 KiB with explicit truncation. External diff/textconv, fsmonitor, optional locks, and lazy fetching are disabled; no diff artifact or automatic diff collection is added. Requires Git with `--no-lazy-fetch` support.
|
|
5
20
|
- `integrate_branch`, `conclude_merge` and `branch_status` accept optional `worktreePath`, so one git agent session can act on several worktrees of the same repository. It must be the absolute root of the primary checkout or a registered linked worktree with the same Git common directory; relative, missing, subdirectory, bare and foreign paths are refused. The tool then runs as if the session cwd were that worktree (control worktree, clean checks, `keepConflicts`, and the per-worktree mutation queue). A `conflict_kept` result now names its `worktreeRoot`.
|
|
6
21
|
|
|
7
22
|
## 0.3.14 - Unreleased
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
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
|
|
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 agent-callable tools that initialize a repository, refresh state, inspect local diffs, manage, integrate, and retire local branches, conclude a kept merge, inspect/create/remove linked worktrees, push the current branch, create GitHub pull requests, read and answer PR feedback, and list and label a PR's changed files.
|
|
20
20
|
|
|
21
21
|
<table align="center">
|
|
22
22
|
<tr>
|
|
@@ -31,10 +31,10 @@ BranchMe is a Pi extension for safe Git repository, branch, and worktree workflo
|
|
|
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:** `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
|
-
- **Explicit history rewrites:** `rebase_branch` runs only when explicitly requested, requires a clean current branch
|
|
34
|
+
- **Explicit history rewrites:** `rebase_branch` runs only when explicitly requested, requires a clean current branch and either an upstream or explicit `baseRef`, 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
|
|
37
|
-
- **Strict tools:** tool schemas reject undocumented properties such as `stash`, `discard`, `owner`, `repo`, or `path`; `baseRef` is supported
|
|
36
|
+
- **Commit-safe:** context collection is read-only, and BranchMe never force-pushes or resets. Ordinary commits happen only through `commit_changes`, which validates a one-line Conventional Commit subject and never push. Guarded index operations and explicitly authorized selected-file restoration are supported without ordinary commits. Explicit `integrate_branch` and `update_from_base` calls may let Git create a standard merge commit for divergent histories; `conclude_merge` stages only the formerly unmerged paths of a kept merge and commits them with Git's prepared merge message.
|
|
37
|
+
- **Strict tools:** tool schemas reject undocumented properties such as `stash`, `discard`, `owner`, `repo`, or `path`; `baseRef` is supported by `create_branch`, `rebase_branch`, and `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
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).
|
|
@@ -103,7 +103,7 @@ A typical BranchMe flow is:
|
|
|
103
103
|
7. 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. A conflict is aborted and its paths listed; with `keepConflicts: true` the conflicted merge stays in progress so a file-editing step can remove the markers, after which `conclude_merge({ action: "conclude" })` commits it or `conclude_merge({ action: "abort" })` restores the branch.
|
|
104
104
|
8. Push the current branch with `push_branch`.
|
|
105
105
|
9. After `push_branch` completes and GitHub can see the branches, create or reuse a matching open pull request with `pull_request`.
|
|
106
|
-
10. Inspect the PR with `pull_request_status({ number: 123 })`.
|
|
106
|
+
10. Inspect the PR with `pull_request_status({ number: 123 })` and `pull_request_checks`. When explicitly authorized, use `merge_pull_request` (or merge on GitHub), then use `land_branch` from the primary checkout. Supply `pullRequestNumber: 123` for squash/rebase merge evidence.
|
|
107
107
|
|
|
108
108
|
For isolated work, a specialized Git subagent can use the explicit worktree workflow:
|
|
109
109
|
|
|
@@ -202,7 +202,7 @@ $EDITOR .env
|
|
|
202
202
|
pi
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
`fetch_remote`, `fetch_branch`, `pull_branch`, `push_branch`, and `land_branch` run git with terminal prompts disabled, so missing credentials fail at once instead of hanging. Your normal Git credentials are tried first; for `https://github.com` remotes the same `GITHUB_TOKEN`/`GH_TOKEN` (process environment or repository `.env`) then answers as a credential helper, passed to git through its environment and never on the command line. A token alone is enough. `rebase_branch` operates on
|
|
205
|
+
`fetch_remote`, `fetch_branch`, `pull_branch`, `push_branch`, and `land_branch` run git with terminal prompts disabled, so missing credentials fail at once instead of hanging. Your normal Git credentials are tried first; for `https://github.com` remotes the same `GITHUB_TOKEN`/`GH_TOKEN` (process environment or repository `.env`) then answers as a credential helper, passed to git through its environment and never on the command line. A token alone is enough. `rebase_branch` operates on a locally available configured upstream or explicit base and makes no network request itself.
|
|
206
206
|
When the current branch already has an upstream, BranchMe pushes an explicit `HEAD:<upstream-branch-ref>` refspec to the configured upstream remote instead of relying on a bare `git push`.
|
|
207
207
|
Run `pull_request` only after `push_branch` has completed; `pull_request` preflights the GitHub `headBranch` and `baseBranch` before creating the PR and fails with retry guidance if a branch is not visible yet or the GitHub `headBranch` commit does not match the local branch.
|
|
208
208
|
|
|
@@ -252,45 +252,141 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
252
252
|
|
|
253
253
|
| Tool | Schema | Behavior |
|
|
254
254
|
| --- | --- | --- |
|
|
255
|
+
| `git_log` | `{ "ref"?: string, "excludeRef"?: string, "paths"?: string[], "after"?: string, "worktreePath"?: string }` | Read 25 locally available commits per page, pinned to captured start/exclusion commits. Reports shallow/unborn history; never fetches. |
|
|
256
|
+
| `git_show` | `{ "ref"?: string, "output"?: "summary" \| "patch" \| "file", "path"?: string, "parent"?: integer, "worktreePath"?: string }` | Inspect a commit or historical blob without checkout; file mode requires a literal path and merge comparisons require an explicit parent. Bounded text, safe binary/oversized summaries. |
|
|
257
|
+
| `git_conflicts` | `{ "path"?: string, "stage"?: integer, "expectedIndex"?: string, "after"?: string, "worktreePath"?: string }` | List conflicts or inspect available base/ours/theirs index versions. Missing stages are explicit; never resolves or stages. |
|
|
258
|
+
| `list_remotes` | `{}` | Inspect configured/effective destinations and a configuration fingerprint without network access. Bounded previews with omissions and unsupported settings reported. |
|
|
259
|
+
| `add_remote` | `{ "name": string, "url": string, "expectedConfig": string }` | Add an unused non-overlapping remote using credential-free HTTPS/SSH; no fetch, overwrite, URL rewrite, or global config change. |
|
|
260
|
+
| `set_upstream` | `{ "branchName": string, "upstream"?: string, "unset"?: boolean, "expectedConfig": string }` | Set one existing remote-tracking upstream or explicitly unset it, preserving refs/files and other branch configuration. |
|
|
261
|
+
| `commit_changes` | `{ "action"?: "gather" \| "commit", "message"?: string, "steeringPrompt"?: string, "paths"?: string[], "confirm"?: boolean, "worktreePath"?: string }` | `gather` (default) is read-only and returns bounded commit context for drafting one subject. `commit` with `message` commits that validated subject; without `message` it drafts one with the active model. `paths` limits staging and committing to exact repo-relative paths. Unsafe files require UI approval or block the commit. Never pushes. |
|
|
262
|
+
| `stage_changes` | `{ "paths": string[], "expectedHead": string \| null, "expectedIndex": string, "worktreePath"?: string }` | Stage exact approved files with fresh inspection expectations. Preserves working files/unrelated index entries; never commits. |
|
|
263
|
+
| `unstage_changes` | `{ "paths": string[], "expectedHead": string \| null, "expectedIndex": string, "worktreePath"?: string }` | Return selected index entries to captured HEAD (or unstage unborn additions), preserving working bytes. Never discards edits or commits. |
|
|
264
|
+
| `restore_paths` | `{ "paths": string[], "expectedHead": string \| null, "expectedIndex": string, "preview"?: boolean, "sourceRef"?: string, "discardChanges"?: boolean, "expectedFiles"?: { "path": string, "fingerprint": string }[], "worktreePath"?: string }` | Preview, then explicitly authorize replacement/deletion of selected tracked working files with fresh per-file expectations. Preserves logical index entries; protects untracked/ignored files. |
|
|
265
|
+
| `revert_changes` | `{ "commit": string, "expectedHead": string, "expectedIndex": string, "worktreePath"?: string }` | Stage one non-merge commit's inverse without committing. Requires clean idle state; conflicts attempt verified abort/restoration. |
|
|
266
|
+
| `pull_request_checks` | `{ "number": integer, "after"?: string }` | Read all run/status states and available host readiness for a captured head, distinguishing partial/unknown/pending/no-check states from success. |
|
|
267
|
+
| `edit_pull_request` | `{ "number": integer, "title"?: string, "body"?: string, "draft"?: boolean }` | Change only explicit metadata fields on an open PR, with verified results and conservative uncertain-outcome handling. |
|
|
268
|
+
| `merge_pull_request` | `{ "number": integer, "expectedHead": string, "mergeMethod": "merge" \| "squash" \| "rebase" }` | Perform an explicitly authorized ready host merge with an expected-head lease; no bypass, automatic cleanup, or remote branch deletion. |
|
|
269
|
+
| `git_status` | `{ "paths"?: string[], "worktreePath"?: string, "after"?: string }` | Enumerate staged, unstaged, untracked, renamed/deleted, and conflicted paths with separate index/worktree states. Up to 50 entries / 24 KiB per page; follow `next` with identical filters/worktree until `complete`. Literal root-relative filters and verified same-repository worktree targeting; no network or mutation. See below. |
|
|
270
|
+
| `git_diff` | `{ "mode"?: "unstaged" \| "staged" \| "range", "baseRef"?: string, "targetRef"?: string, "mergeBase"?: boolean, "output"?: "patch" \| "stat" \| "name-status", "paths"?: string[], "contextLines"?: integer, "worktreePath"?: string }` | Read local diffs without staging or changing files/refs. Defaults to unstaged patch output. Range mode requires `baseRef`, defaults `targetRef` to `HEAD`, and optionally compares from the unique merge base. Only range mode accepts ref/merge-base fields. Literal root-relative paths, 0–20 context lines (default 3), same-repository worktree targeting, and bounded redacted display. See below. |
|
|
255
271
|
| `list_branches` | `{ "kind"?: "local" \| "remote-tracking", "patterns"?: string[] }` | Read up to 200 matching local/cached remote-tracking refs with commits, upstream counts, symbolic refs, and occupancy. Patterns use Git branch-list globs on names (`origin/topic` for remote refs), match any supplied pattern, and filter before limits. Omit filters for the original full inventory. Accepts 1–25 nonblank patterns of at most 512 characters. 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. |
|
|
256
272
|
| `fetch_remote` | `{ "remote"?: string, "prune"?: boolean }` | Refresh all heads for one configured remote (default `origin`) into its remote-tracking namespace. Prune defaults to false; true deletes only that remote's stale cached branch refs. Atomic ref updates, no configured refmap, tags, tag pruning, or submodules. Never changes local refs, checkout, upstream settings, or working-tree files. Files ref backend required for dangling-alias safety. |
|
|
257
|
-
| `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. |
|
|
258
|
-
| `update_from_base` | `{ "baseBranch": string, "remote"?: string, "keepConflicts"?: boolean }` | Fetch the exact remote base (remote defaults to `origin`) and merge its captured commit into the clean current feature. Uses verified normal-merge policy; a `conflict` is automatically aborted with its paths listed, or with `keepConflicts: true` reported as `conflict_kept` and left in progress (MERGE_HEAD set, conflicted paths unmerged) for `conclude_merge`. Never rebases, pushes, or changes upstream configuration. |
|
|
273
|
+
| `track_branch` | `{ "branchName": string, "remote"?: string, "remoteBranch"?: string, "worktreePath"?: 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. |
|
|
274
|
+
| `update_from_base` | `{ "baseBranch": string, "remote"?: string, "keepConflicts"?: boolean, "worktreePath"?: 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; a `conflict` is automatically aborted with its paths listed, or with `keepConflicts: true` reported as `conflict_kept` and left in progress (MERGE_HEAD set, conflicted paths unmerged) for `conclude_merge`. Never rebases, pushes, or changes upstream configuration. |
|
|
259
275
|
| `conclude_merge` | `{ "action": "conclude" \| "abort", "worktreePath"?: string }` | Finish or abandon the in-progress merge on the current checkout. `conclude` refuses default/custom-sized `<<<<<<<`, `|||||||`, `=======`, or `>>>>>>>` marker lines in unmerged working-tree paths and staged blobs (including CRLF and binary content, naming the paths), stages only the currently unmerged paths, rechecks the candidate index, commits with `git commit --no-edit`, and verifies MERGE_HEAD is gone and HEAD is a two-parent merge of the previous HEAD and MERGE_HEAD; unrelated unstaged changes stay unstaged. `abort` runs `git merge --abort` and verifies the restored HEAD, cleared operation state, and clean tree. Refuses when no merge is in progress. `worktreePath` (an absolute worktree root of the same repository) lets one session act on another worktree; omit it for the current one. |
|
|
260
276
|
| `pull_request_files` | `{ "number": integer }` | List a pull request's first 100 changed files (path, change type, additions, deletions; the rest counted) and its current labels. Read-only. |
|
|
261
277
|
| `label_pull_request` | `{ "number": integer, "labels": string[] }` | Add 1 to 5 labels that already exist in the repository to a pull request; an unknown label is refused before anything changes, existing labels stay. |
|
|
262
278
|
| `reply_pull_request_feedback` | `{ "number": integer, "body": string, "threadId"?: string, "resolve"?: boolean }` | Answer PR feedback on an open, merged or closed PR of this repository (forks included). With `threadId` (from `pull_request_feedback`) it replies in that review thread after verifying the thread belongs to the PR, and `resolve: true` then resolves it; without `threadId` it posts one conversation comment after confirming the number is a pull request, not an issue. Body is Markdown, at most 4,000 characters. Never edits, deletes, approves, merges, or closes. |
|
|
263
|
-
| `pull_request_feedback` | `{ "number"?: integer, "headBranch"?: string }` | Read what a same-repository PR asks to be fixed in one GraphQL request: unresolved, current review threads with their comments (resolved and outdated threads are counted only), review summaries with a body or requested changes, the latest 50 conversation comments, and the head commit's failing checks with up to 20 failure and warning annotations each (`path:line LEVEL: message`). Threads and conversation comments carry their GraphQL ids, every comment its author and time, and comments by the token's own user are marked `(you)`, so a caller can reply to a thread and tell new comments from its own replies. For up to three failing GitHub Actions jobs it adds the log lines leading to the last `##[error]`. A job log that cannot be read is reported per check. No mutations. |
|
|
264
|
-
| `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. |
|
|
279
|
+
| `pull_request_feedback` | `{ "number"?: integer, "headBranch"?: string, "worktreePath"?: string }` | Read what a same-repository PR asks to be fixed in one GraphQL request: unresolved, current review threads with their comments (resolved and outdated threads are counted only), review summaries with a body or requested changes, the latest 50 conversation comments, and the head commit's failing checks with up to 20 failure and warning annotations each (`path:line LEVEL: message`). Threads and conversation comments carry their GraphQL ids, every comment its author and time, and comments by the token's own user are marked `(you)`, so a caller can reply to a thread and tell new comments from its own replies. For up to three failing GitHub Actions jobs it adds the log lines leading to the last `##[error]`. A job log that cannot be read is reported per check. No mutations. |
|
|
280
|
+
| `pull_request_status` | `{ "number"?: integer, "headBranch"?: string, "worktreePath"?: 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. |
|
|
265
281
|
| `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. |
|
|
266
282
|
| `branch_status` | `{ "ancestry"?: { "sourceBranch": string, "targetBranch": string }, "worktreePath"?: 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. `worktreePath` (an absolute worktree root of the same repository) lets one session act on another worktree; omit it for the current one. |
|
|
267
283
|
| `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. |
|
|
268
284
|
| `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. |
|
|
269
|
-
| `remove_worktree` | `{ "worktreePath": string, "deleteIgnored"?: boolean, "discardChanges"?: boolean }` |
|
|
285
|
+
| `remove_worktree` | `{ "worktreePath": string, "deleteIgnored"?: boolean, "discardChanges"?: boolean }` | Deletes an explicitly selected linked checkout directory but keeps its local branch at the same commit. Uncommitted changes and ignored files are refused by default. `deleteIgnored: true` permits deleting all ignored entries, including possible `.env` and `.pi/`, and reports their top-level paths; `discardChanges: true` permits discarding uncommitted changes and lists them in `discardedPaths`. Each destructive option requires explicit user authorization. |
|
|
270
286
|
| `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. |
|
|
271
|
-
| `land_branch` | `{ "sourceBranch": string, "targetBranch": string, "remote"?: string, "worktreePath"?: string, "pullRequestNumber"?: integer }` |
|
|
272
|
-
| `change_branch` | `{ "branchName": string }` | Validates `branchName`, requires `refs/heads/<branchName>` to exist locally, rejects staged, unstaged, or unmerged tracked changes (naming the paths), and runs `git switch <branchName>`. Untracked files such as `.env` do not block; git itself refuses a switch that would overwrite one. |
|
|
273
|
-
| `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. |
|
|
274
|
-
| `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. |
|
|
275
|
-
| `rebase_branch` | `{}` |
|
|
287
|
+
| `land_branch` | `{ "sourceBranch": string, "targetBranch": string, "remote"?: string, "worktreePath"?: string, "pullRequestNumber"?: integer }` | Cleanup after a verified merge, not a PR merge operation: fetch the target, verify ancestry or exact merged-PR evidence, remove a clean linked worktree including ignored files, delete the local source at its expected commit, and independently attempt fast-forward target sync. Never deletes remote branches. Default remote `origin`; omitted path finds the source's linked worktree. Inspect each receipt because cleanup and sync can have different outcomes. |
|
|
288
|
+
| `change_branch` | `{ "branchName": string, "worktreePath"?: string }` | Validates `branchName`, requires `refs/heads/<branchName>` to exist locally, rejects staged, unstaged, or unmerged tracked changes (naming the paths), and runs `git switch <branchName>`. Untracked files such as `.env` do not block; git itself refuses a switch that would overwrite one. |
|
|
289
|
+
| `fetch_branch` | `{ "remote"?: string, "branch"?: string, "worktreePath"?: 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. |
|
|
290
|
+
| `pull_branch` | `{ "worktreePath"?: string }` | 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. |
|
|
291
|
+
| `rebase_branch` | `{ "baseRef"?: string, "worktreePath"?: string }` | Replays local commits onto cached upstream by default, or an explicitly captured local/remote-tracking/full-commit base without changing upstream. Requires history-rewrite authorization; no fetch/force-push. Explicit-base mode verifies restoration and refuses local-only ignored residue. |
|
|
276
292
|
| `integrate_branch` | `{ "sourceBranch": string, "targetBranch": string, "keepConflicts"?: boolean, "worktreePath"?: string }` | Integrates one exact existing local source branch into one distinct existing local target branch. The clean active control worktree must already have the target checked out. It returns `already_integrated`, `fast_forward`, `merge_commit`, or a `conflict` only after automatic abort and verified restoration; with `keepConflicts: true` a conflict is reported as `conflict_kept` and left in progress (MERGE_HEAD set, conflicted paths unmerged) for `conclude_merge`. It never fetches or pushes. `worktreePath` (an absolute worktree root of the same repository) lets one session act on another worktree; omit it for the current one. |
|
|
277
|
-
| `create_branch` | `{ "branchName": string }` |
|
|
278
|
-
| `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. A branch whose upstream is a differently named `main`, `master`, `trunk`, `develop`, or origin default branch is published to its own name instead and tracks it. |
|
|
279
|
-
| `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 reuses an exact matching open PR or creates one in the resolved current repository. Existing PR metadata is preserved. Omitted fields require `BRANCHME_PR_AUTOFILL=true`; branch refs must be distinct, exist locally, and cannot use `owner:branch`. |
|
|
293
|
+
| `create_branch` | `{ "branchName": string, "baseRef"?: string, "worktreePath"?: string }` | Creates at current HEAD by default or an explicitly captured local/remote-tracking/full-commit base. Explicit bases require tracked-clean idle state; no fetch or inferred upstream. |
|
|
294
|
+
| `push_branch` | `{ "worktreePath"?: string }` | 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. A branch whose upstream is a differently named `main`, `master`, `trunk`, `develop`, or origin default branch is published to its own name instead and tracks it. |
|
|
295
|
+
| `pull_request` | `{ "headBranch"?: string, "baseBranch"?: string, "title"?: string, "body"?: string, "draft"?: boolean, "worktreePath"?: string }` | Preflights GitHub branch visibility and verifies the GitHub `headBranch` commit matches the local branch, then reuses an exact matching open PR or creates one in the resolved current repository. Existing PR metadata is preserved. Omitted fields require `BRANCHME_PR_AUTOFILL=true`; branch refs must be distinct, exist locally, and cannot use `owner:branch`. |
|
|
280
296
|
|
|
281
297
|
`conclude_merge` supports only single-head merges and refuses multi-head merges before mutation. Its marker check also covers manually staged resolutions and clean-filter output. Marker-like lines (including Markdown setext underlines) are conservatively refused; configured marker sizes above 4,096 are refused. Git commits the **entire index**, including already-staged merge entries: do not stage unrelated edits during the merge. Unrelated unstaged edits remain outside the commit. A failed/lost commit response reports uncertain postconditions; inspect `HEAD`, `MERGE_HEAD`, and the index before retrying.
|
|
282
298
|
|
|
283
|
-
All schemas reject additional properties. `fetch_remote` accepts only optional `remote` and `prune`, never arbitrary refspecs or tag/force/checkout controls. `list_branches` accepts only optional `kind` and bounded `patterns`; it remains read-only. `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`
|
|
299
|
+
All schemas reject additional properties. `fetch_remote` accepts only optional `remote` and `prune`, never arbitrary refspecs or tag/force/checkout controls. `list_branches` accepts only optional `kind` and bounded `patterns`; it remains read-only. `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` accepts only an optional worktree selector; `rebase_branch` additionally accepts an explicit read-only `baseRef`. Neither accepts force, refspec, autostash, or interactive rebase controls. `integrate_branch` requires exactly `sourceBranch` and `targetBranch` plus optional boolean `keepConflicts`; it accepts no repository, path, remote, strategy, message, squash, signing, commit, continuation, abort, force, fetch, push, deletion, or worktree controls. `conclude_merge` requires exactly `action` (`conclude` or `abort`) and accepts no path list, commit message, strategy, `--ours`/`--theirs`, or force 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`. Generic `continue_merge` and `abort_merge` tools are not available; `conclude_merge` is the only merge-continuation surface and only commits paths whose markers are already gone.
|
|
284
300
|
|
|
285
301
|
---
|
|
286
302
|
|
|
303
|
+
See [shell-free workflows](docs/SHELL_FREE_WORKFLOWS.md) for history/conflict inspection, cross-worktree coordination, remote/tracking setup, index control, guarded restore/revert, and PR checks/edit/merge. Ordinary commits are never created automatically; only an explicit `commit_changes` call commits.
|
|
304
|
+
|
|
305
|
+
`worktreePath` selects a verified live checkout of the same repository without changing Pi's cwd. Branch-inferred PR status/feedback reject a selector with an exact `number`. Mutating tool calls share an outer process-wide coordinator before helper root/common-directory queues; external Git processes/extensions are not locked out.
|
|
306
|
+
|
|
307
|
+
### Complete file status without Bash
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
git_status({})
|
|
311
|
+
git_status({ after: "<next from the previous page>" }) // repeat filters/worktreePath if supplied
|
|
312
|
+
git_status({ paths: ["src", "README.md"] })
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`git_status` is separate from the bounded `branch_status`/automatic summary and from content-reading `git_diff`. It lists all Git-reported changed paths across pages, including staged-only paths. `total` counts matching records; `hasChanges` describes the whole checkout within scope. An empty filtered result is not a cleanliness claim. Ignored files and submodule working-tree contents are excluded; recorded gitlink changes remain visible. Git's assume-unchanged/skip-worktree policies still apply.
|
|
316
|
+
|
|
317
|
+
Continuation is bound to the canonical checkout, normalized filters, HEAD, logical index entries/flags, operation state, status records, and available changed-path filesystem metadata. A stale cursor requires restarting without `after`; a checkout changing during inspection fails instead of returning a misleading page. This is observation, not a lock against external Git or file writers. Each Git response is capped at 4 MiB; exceeding that limit fails rather than claiming partial status is complete.
|
|
318
|
+
|
|
319
|
+
Results include `head`, a SHA-256 logical `index` expectation, and `snapshot`. Explicit `paths` additionally request content/mode fingerprints for reusable returned files (up to 8 MiB each; no content is returned); missing files and symlinks have distinct fingerprints, while unsupported/oversized objects report no fingerprint. These identities describe observed state for guarded operations; they do not authorize mutations. Redacted, abbreviated, control-containing, invalid-encoding, or symlink-ancestor paths have `reusablePath: false` and must never be copied into mutation calls. New Git support for `--no-lazy-fetch` is required. Automatic context is unchanged.
|
|
320
|
+
|
|
287
321
|
## Workflow and Boundaries
|
|
288
322
|
|
|
323
|
+
### Choosing between similar tools
|
|
324
|
+
|
|
325
|
+
Tool names and parameter contracts remain stable. Short descriptions state the operation and important effects without repeating tool names or schema details. Schemas explain arguments; optional guidelines cover essential safety and sequencing. Brief prompt snippets omit the name prefix Pi adds automatically. Standalone guidelines name their tool because Pi renders them ungrouped.
|
|
326
|
+
|
|
327
|
+
| Intent | Choose | Important distinction |
|
|
328
|
+
| --- | --- | --- |
|
|
329
|
+
| Enumerate every changed path | `git_status` | Follow `next` until complete; summary context omits paths. |
|
|
330
|
+
| Show changed file contents | `git_diff` | Status metadata is not a diff; untracked file contents are excluded. |
|
|
331
|
+
| Refresh observed repository status or check ancestry | `branch_status` | Reads local/cached Git state and related PR metadata; never fetches Git refs. Ancestry checks may be independent. |
|
|
332
|
+
| Find branch names | `list_branches` | Cached inventory; run `fetch_remote` first when fresh remote discovery is needed. |
|
|
333
|
+
| Fetch one known remote branch | `fetch_branch` | Updates its tracking ref, not the local branch or checkout. |
|
|
334
|
+
| Fetch all remote branch names and heads | `fetch_remote` | Refreshes one remote's tracking namespace; pruning is opt-in and local-cache-only. |
|
|
335
|
+
| Switch to an existing local branch | `change_branch` | Does not create a branch or fetch one. |
|
|
336
|
+
| Create and switch to a new branch | `create_branch` | Defaults to current HEAD; optional explicit base never fetches or moves that base. |
|
|
337
|
+
| Join an existing remote branch | `track_branch` | Fetches, creates a new local tracking branch, and switches; cannot configure an existing local branch. |
|
|
338
|
+
| Update the current branch from its own upstream | `pull_branch` | Fetches and fast-forwards only; divergence fails. |
|
|
339
|
+
| Merge a fresh remote main into the current feature | `update_from_base` | Fetches and merges; may create a merge commit, never rebases or changes upstream configuration. |
|
|
340
|
+
| Merge a local source into a local target | `integrate_branch` | Target must already be checked out; never fetches. |
|
|
341
|
+
| Rebase onto upstream or a chosen local base | `rebase_branch` | Rewrites local commits; no fetch or force-push, and does not change upstream. |
|
|
342
|
+
| Finish or abandon an existing merge | `conclude_merge` | Resolve conflicts by editing first to conclude; commits the entire index or aborts the merge. |
|
|
343
|
+
| Push existing commits on the current branch | `push_branch` | Accepts an optional worktree selector, not a caller-supplied push target; never commits. |
|
|
344
|
+
| Create or reuse a matching GitHub PR | `pull_request` | Push first; does not push, merge, or edit an existing PR. |
|
|
345
|
+
| Verify a PR was merged | `pull_request_status` | Lifecycle evidence, not proof that CI or review requirements passed. |
|
|
346
|
+
| Read review requests and failing CI | `pull_request_feedback` | Read-only; use `reply_pull_request_feedback` to post a reply or comment. |
|
|
347
|
+
| Clean up after a verified merge | `land_branch` | Deletes the local source and clean linked worktree, including ignored files; does not merge the PR. |
|
|
348
|
+
| Remove only a linked checkout | `remove_worktree` | Retains its branch; destructive options require explicit authorization. |
|
|
349
|
+
| Delete only a local branch | `retire_branch` | Requires an unoccupied branch and expected commit; does not remove its checkout or remote branch. |
|
|
350
|
+
|
|
351
|
+
Wait for dependent calls to finish: fetch before inspecting fresh remote refs, switch before pulling, push before creating a PR, and integrate before checking its result. Independent ancestry checks do not require a preceding merge. A kept conflict must be resolved and concluded or aborted before another Git mutation.
|
|
352
|
+
|
|
353
|
+
### Inspect local diffs
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
git_diff({}) // unstaged: index -> working tree
|
|
357
|
+
git_diff({ mode: "staged", paths: ["src", "README.md"] }) // HEAD -> index
|
|
358
|
+
git_diff({ mode: "range", baseRef: "main", targetRef: "HEAD" }) // main -> HEAD
|
|
359
|
+
git_diff({ mode: "range", baseRef: "origin/main", mergeBase: true, output: "stat" }) // origin/main...HEAD
|
|
360
|
+
git_diff({ output: "name-status", worktreePath: "/absolute/path/to/linked-worktree" })
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`git_diff` requires Git with `--no-lazy-fetch` support and fails closed on older Git. It never stages, commits, fetches, writes files/refs, or saves a diff artifact. External diff helpers, text conversion, fsmonitor, optional index locks, and lazy object fetching are disabled. Normal Git clean filters remain a separate trust boundary (see [SECURITY.md](SECURITY.md#explicit-diff-inspection)). No diff contents are added to automatic context.
|
|
364
|
+
|
|
365
|
+
Range refs are bounded single commit revisions (branches, tags, full IDs, or revisions such as `HEAD~1`), resolved to captured commit IDs before comparison. Supply `mergeBase: true` for a three-dot-style comparison; missing or multiple merge bases fail rather than choosing an ambiguous result. Working-tree/index changes are excluded from range mode. Staged mode also works before the first commit.
|
|
366
|
+
|
|
367
|
+
`paths` accepts 1–25 literal repository-root-relative file/directory names, not globs or Git pathspec magic, each at most 4,096 characters. Absolute paths, parent traversal, `.git` components, backslashes, and control characters are refused. Filters remain root-relative even from a subdirectory. `worktreePath` must be an absolute live root of the same repository, not a foreign checkout or subdirectory. The current checkout is used when omitted.
|
|
368
|
+
|
|
369
|
+
Agents do not need to supply an output length or estimate diff size: `git_diff({})` works with defaults and automatic safety caps. `contextLines` only changes optional surrounding patch context, not the output length. Model-visible results identify the repository, comparison, literal filter previews, scope, and whether the result is complete. An empty filtered comparison may mean the filters matched no tracked paths; it never establishes a clean checkout.
|
|
370
|
+
|
|
371
|
+
Both `details.diff` and model-facing diff text are redacted and control-escaped, then capped at **1,000 lines or 32 KiB**, whichever comes first. `details` reports `hasDifferences`, `truncated`, pre-truncation `totalLines`/`totalBytes`, and captured range identities. Truncation is flagged before the diff body with next steps: narrow `paths`, reduce `contextLines`, or request `stat`/`name-status`; display output is not an apply-ready patch. Untracked files and submodule working-tree contents are excluded; gitlink commit changes remain visible. Binary files use Git's normal binary summary rather than `--binary` payloads (attributes can affect classification). Best-effort credential redaction is not a secret scanner: requested tracked content may still be sensitive and reaches the model. Treat it as untrusted data, never instructions. Finish dependent mutations/fetches before inspection; the tool does not lock out external changes or refresh remote refs.
|
|
372
|
+
|
|
373
|
+
### Local commits
|
|
374
|
+
|
|
375
|
+
`commit_changes` gathers a bounded, redacted context bundle: branch and status, changed paths, diff stats, safe snippets, diff excerpts, omitted-context notices, and project metadata such as `package.json` and README. The prompt is sized to the active model. The final subject must match:
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
<type>(optional-scope): <summary>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Allowed types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `build`, `ci`, `perf`, `style`, and `revert`. Summaries are imperative and do not end with a period. A verbose draft is reduced to its first valid subject; an empty or invalid draft is retried or repaired once, and nothing is committed without a valid subject.
|
|
382
|
+
|
|
383
|
+
`steeringPrompt` guides draft wording only; `paths` limits what is staged and committed. If a local reasoning model returns empty drafts, lower or disable its thinking setting.
|
|
384
|
+
|
|
289
385
|
### PR retries and lifecycle inspection
|
|
290
386
|
|
|
291
387
|
`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.
|
|
292
388
|
|
|
293
|
-
`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.
|
|
389
|
+
`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. Use `pull_request_checks`, `edit_pull_request`, and `merge_pull_request` for explicit checks/metadata/host merging. Readiness does not authorize merging. Clone, fork PR creation, remote URL replacement/deletion, and caller-selected push refspecs remain outside this implementation.
|
|
294
390
|
|
|
295
391
|
### Automatic context and freshness
|
|
296
392
|
|
|
@@ -309,7 +405,7 @@ The snapshot is fresh at agent start but is not live. A fetch, branch switch, pu
|
|
|
309
405
|
|
|
310
406
|
Related-PR metadata does not come from Git alone. When repository, branch, and credentials resolve, automatic collection and explicit `branch_status` may make an authenticated `GET /repos/{owner}/{repo}/pulls?state=open&head={owner}:{branch}&per_page=1` request. Without a token there is no unauthenticated fallback or GitHub request; the PR field is reported as unavailable while local Git context remains usable.
|
|
311
407
|
|
|
312
|
-
Repository paths, commit subjects, branch names, and PR titles are treated as untrusted metadata.
|
|
408
|
+
Repository paths, commit subjects, branch names, and PR titles are treated as untrusted metadata. Automatic context redacts token values, escapes control characters, quotes and bounds values before prompt insertion, and never captures diffs or file contents. Explicit `git_diff` is the separate opt-in content inspection surface. Staged files contribute only to the staged count; the path list is limited to unstaged and untracked entries.
|
|
313
409
|
|
|
314
410
|
Use BranchMe from pi prompts or automation that drives pi with explicit tool calls:
|
|
315
411
|
|
|
@@ -333,7 +429,7 @@ For a source not already reachable from the target, BranchMe runs this fixed nor
|
|
|
333
429
|
git -c rerere.enabled=false merge --ff --no-edit --no-autostash --no-rerere-autoupdate --no-overwrite-ignore refs/heads/<sourceBranch>
|
|
334
430
|
```
|
|
335
431
|
|
|
336
|
-
This policy permits a fast-forward or lets Git create a standard two-parent merge commit for divergent histories.
|
|
432
|
+
This policy permits a fast-forward or lets Git create a standard two-parent merge commit for divergent histories. Integration does not create user-authored commits, accept a merge message, or expose strategy, squash, unrelated-history, signing, force, or commit controls. Before mutation, it rejects a non-empty `branch.<targetBranch>.mergeOptions` setting because those branch-specific defaults could silently change the fixed policy. Autostash is disabled, rerere is disabled so recorded resolutions are not applied or updated, and ignored files may not be overwritten. Repository-configured hooks, custom merge drivers, clean/smudge filters, and signing policy remain active; those Git extension points may execute arbitrary local commands or network operations under the user's identity.
|
|
337
433
|
|
|
338
434
|
The structured status is one of:
|
|
339
435
|
|
|
@@ -470,9 +566,9 @@ BranchMe operates only on the repository where pi is running:
|
|
|
470
566
|
- `change_branch` switches only to existing local branches and has no `force`, `stash`, `discard`, remote, or path input.
|
|
471
567
|
- `fetch_branch` uses an explicit source-to-remote-tracking refspec with tags and submodule recursion disabled and does not change local branches or working-tree files. Without arguments it requires a configured upstream; with an explicit `branch` (and optional configured `remote`, default `origin`) it refreshes only `refs/remotes/<remote>/<branch>` and never touches upstream configuration.
|
|
472
568
|
- `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.
|
|
473
|
-
- `rebase_branch` requires
|
|
569
|
+
- `rebase_branch` requires clean state and an upstream or explicit base, disables autostash/multi-ref updates, and attempts abort on failure. Explicit-base mode verifies restoration and leaves publication outside BranchMe's force-push-free surface.
|
|
474
570
|
- `integrate_branch` merges one captured local source ref into the already-current clean local target with the fixed policy documented above. It verifies repository identity, refs, ancestry, clean state, and cleanup without fetching or pushing.
|
|
475
|
-
- `create_branch`
|
|
571
|
+
- `create_branch` defaults to current `HEAD` and supports an explicit captured read-only base; it never fetches, resets that base, or infers tracking.
|
|
476
572
|
- `list_worktrees` is an explicit, read-only repository inventory; it is intentionally absent from automatic active-worktree context.
|
|
477
573
|
- `create_worktree` may create a linked checkout outside the active checkout only after canonical path and current-repository boundary checks; its optional `baseRef` is resolved read-only to a commit and never checked out or reset, and dependent worktree calls must wait for its verified handoff.
|
|
478
574
|
- `remove_worktree` passes Git only a freshly verified canonical linked-worktree path, never uses force, and retains the branch.
|
|
@@ -481,7 +577,7 @@ BranchMe operates only on the repository where pi is running:
|
|
|
481
577
|
- `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`.
|
|
482
578
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, `pull_request` fails closed.
|
|
483
579
|
|
|
484
|
-
BranchMe intentionally does **not** create user-authored commits
|
|
580
|
+
Outside `commit_changes`, BranchMe intentionally does **not** create user-authored commits or accept or generate commit messages. It never forces checkout, stash, force-push, reset, copy ignored/untracked files between worktrees, or delete branches during standalone `remove_worktree`. Explicit `stage_changes`/`unstage_changes` manage approved index entries; `restore_paths` requires a preview, fresh file expectations, and explicit discard authorization. `revert_changes` stages an inverse without committing; `conclude_merge` stages only formerly unmerged paths. 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.
|
|
485
581
|
|
|
486
582
|
---
|
|
487
583
|
|
|
@@ -531,8 +627,8 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
531
627
|
| 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. |
|
|
532
628
|
| 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. |
|
|
533
629
|
| Linked-worktree agent cannot find credentials | Pass credentials through the process environment. BranchMe does not copy repository-root `.env` or other ignored/untracked files. |
|
|
534
|
-
| Dirty worktree before branch switch, pull, rebase, or integration | Commit,
|
|
535
|
-
| Fetch, pull, or rebase has no upstream |
|
|
630
|
+
| Dirty worktree before branch switch, pull, rebase, or integration | Commit through the owning workflow, preserve edits, or explicitly authorize guarded `restore_paths` before using `change_branch`, `pull_branch`, `rebase_branch`, or a target control worktree for `integrate_branch`. |
|
|
631
|
+
| Fetch, pull, or rebase has no upstream | Use `list_remotes` and `set_upstream` for an existing tracking ref, or publish with `push_branch`. An explicit rebase base needs no upstream. |
|
|
536
632
|
| Unknown remote issue branch or stale branch listing | Run `fetch_remote` (optionally `prune: true`), wait for success, then `list_branches` with `kind: "remote-tracking"` and issue-name patterns. For reftable or unsafe symbolic destinations, inspect repository refs outside BranchMe. |
|
|
537
633
|
| 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. |
|
|
538
634
|
| Rebase fails or conflicts | `rebase_branch` automatically attempts `git rebase --abort`. Inspect repository state before continuing if automatic cleanup also fails. |
|
|
@@ -554,7 +650,12 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
554
650
|
| 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. |
|
|
555
651
|
| 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. |
|
|
556
652
|
| Repository mismatch | Make `origin` and `GITHUB_REPOSITORY` refer to the same `owner/repo`. |
|
|
557
|
-
| Need a user-authored commit | Use
|
|
653
|
+
| Need a user-authored commit | Use `commit_changes`. Other tools never accept commit messages; `integrate_branch`, `update_from_base`, and `conclude_merge` may let Git create a standard merge commit. |
|
|
654
|
+
| No active model or API key for a drafted commit | Select or configure a Pi model, or pass an explicit `message` to `commit_changes action="commit"`. |
|
|
655
|
+
| `confirm=true requires a UI-capable Pi mode` | Run Pi with the TUI, or omit `confirm` in non-UI mode. |
|
|
656
|
+
| Commit flagged for secret files/tokens or unreadable files | In the TUI, approve only reviewed safe fixtures or placeholders. In non-UI mode, remove the file or token, make the file readable, or commit manually. |
|
|
657
|
+
| Git status changed while committing | Rerun the commit so it gathers fresh context. |
|
|
658
|
+
| Empty, thinking-only, length-stopped, or invalid model draft | The draft is retried or repaired once. If it still fails, reduce the change size, lower model thinking, add steering text, or use `commit_changes action="gather"` and commit with an explicit `message`. |
|
|
558
659
|
| Other extensions interfere | Test with `pi --no-extensions -e .`. |
|
|
559
660
|
|
|
560
661
|
---
|
|
@@ -569,7 +670,7 @@ npm run check:pack
|
|
|
569
670
|
printf '/branchme help\n/quit\n' | pi --no-extensions -e .
|
|
570
671
|
```
|
|
571
672
|
|
|
572
|
-
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
|
|
673
|
+
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 every tool in the canonical `BRANCHME_TOOL_NAMES` inventory is visible through `pi.getAllTools()` with strict schemas and prompt metadata, including `integrate_branch`, `retire_branch`, `conclude_merge`, and targeted `branch_status.ancestry`, with no generic `continue_merge` or `abort_merge` 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).
|
|
573
674
|
|
|
574
675
|
Refresh TUI captures intentionally with:
|
|
575
676
|
|
package/SECURITY.md
CHANGED
|
@@ -17,14 +17,14 @@ Implemented git mutations are limited to:
|
|
|
17
17
|
|
|
18
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.
|
|
19
19
|
- `change_branch`: `git switch <branchName>` after branch-name validation, local `refs/heads/<branchName>` verification, and clean-worktree preflight.
|
|
20
|
-
- `create_branch`: `git switch -c <branchName>` from current `HEAD`
|
|
20
|
+
- `create_branch`: `git switch -c <branchName>` from current `HEAD` by default, or an explicitly captured local/remote-tracking/full-commit base. Explicit bases require tracked-clean idle state and ignored-overwrite protection; no fetch or inferred upstream is performed.
|
|
21
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
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; with explicit `keepConflicts: true` a conflicted merge is instead verified and left in progress (MERGE_HEAD equals the captured base commit, HEAD unchanged) for `conclude_merge`. 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.
|
|
23
23
|
- `conclude_merge`: on the current checkout with MERGE_HEAD present, `action: "conclude"` lists unmerged paths with `git diff --name-only --diff-filter=U -z`, refuses if `git --literal-pathspecs grep` finds a default/custom-sized conflict marker in those working-tree files or staged changed blobs (including CRLF and binary content), then runs `git --literal-pathspecs add -- <those paths>` and checks the candidate index again before `git commit --no-edit`, then verifies MERGE_HEAD is gone and HEAD is an exact two-parent merge of the previous HEAD and MERGE_HEAD on the same branch. It never uses `commit -a`, never stages other paths, accepts no message, and runs repository hooks as Git normally would. `action: "abort"` runs `git merge --abort` and verifies the same restoration as the automatic abort path. It refuses multi-head merges before mutation, honors cancellation until staging begins, and reports lost commit responses as uncertain postconditions. The commit includes the entire index, including previously staged entries; callers must not stage unrelated edits during a merge. Marker-like lines are conservatively refused, and configured marker sizes above 4,096 are rejected.
|
|
24
24
|
- `fetch_remote`: atomic fetch of `+refs/heads/*:refs/remotes/<remote>/*` for one validated configured remote, with `--refmap=`, `--no-tags`, `--no-prune-tags`, `--no-recurse-submodules`, and `--no-auto-maintenance`. Explicit `--no-prune` is the default even when Git configuration enables pruning; optional `prune: true` adds `--prune` only within that remote's heads mapping. Configured extra fetch mappings, tag options, and tag-pruning settings cannot expand this scope. Rewritten tips may update cached remote-tracking refs; no local branch, tag, checkout, or upstream configuration is changed. A negative `^refs/heads/HEAD` refspec prevents writes through conventional remote HEAD aliases. Preflight rejects unsafe symbolic destinations, symlinked/malformed loose-ref namespaces, and overlapping configured remote names such as `origin` and `origin/team` so pruning cannot consume another remote's namespace. Since Git silently omits dangling symrefs from `for-each-ref`, bounded loose-ref inspection is also required, and non-files ref backends such as reftable are refused. The common-Git-directory queue serializes these fetches across linked worktrees; external processes can still race preflight. Fetch failure/cancellation is not a rollback guarantee: inspect refs before retrying.
|
|
25
25
|
- `fetch_branch`: `git fetch --no-tags --no-recurse-submodules <remote> <remoteBranchRef>:<remoteTrackingRef>`. With no arguments it validates and fetches the current branch's configured upstream; with explicit `branch` and optional configured `remote` (default `origin`) it fetches that exact remote branch. The explicit destination is limited to the selected remote-tracking ref, so local branches and working-tree files are not changed.
|
|
26
26
|
- `pull_branch`: `git pull --ff-only --no-rebase --no-autostash <upstreamRemote> <upstreamBranchRef>` for the clean current branch after validating its configured upstream target.
|
|
27
|
-
- `rebase_branch`: `git rebase --no-autostash --no-update-refs <upstream>`
|
|
27
|
+
- `rebase_branch`: `git rebase --no-autostash --no-update-refs <upstream>` by default, or an explicitly captured local base with additional no-autosquash/no-rebase-merges policy. Explicit-base mode requires clean idle state, refuses ignored local-only residue, preserves configuration/unrelated refs, and verifies abort/restoration. It rewrites local commits and automatically attempts `git rebase --abort` without the cancelled caller signal if the rebase fails or is killed.
|
|
28
28
|
- `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. Conflicts are automatically aborted with restoration verified by default; explicit `keepConflicts: true` instead returns `conflict_kept` after verifying the captured paths, unchanged target HEAD, source MERGE_HEAD, repository identity, and single-merge operation state. Use `conclude_merge` to finish after resolution or abort before any other Git mutation.
|
|
29
29
|
- `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.
|
|
30
30
|
- `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.
|
|
@@ -34,11 +34,39 @@ Implemented git mutations are limited to:
|
|
|
34
34
|
|
|
35
35
|
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.
|
|
36
36
|
|
|
37
|
-
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.
|
|
37
|
+
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. These helper-level queues are process-local; mutating tool calls additionally use an outer process-wide coordinator across linked worktrees. Other Pi processes, extensions, and external Git processes are not locked. 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.
|
|
38
38
|
|
|
39
|
-
BranchMe rejects staged, unstaged, or unmerged tracked changes before `change_branch`, `pull_branch`, `rebase_branch`, `track_branch`, and `update_from_base`, naming the affected paths. Untracked files do not block these preflights; Git still refuses non-ignored file collisions, and tracking/base-update/landing operations also protect ignored files. Standalone `integrate_branch` still requires a fully clean control worktree, including untracked files. Standalone `remove_worktree` rejects any staged, unstaged, untracked, or unmerged entry in a linked 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.
|
|
39
|
+
BranchMe rejects staged, unstaged, or unmerged tracked changes before `change_branch`, `pull_branch`, default upstream `rebase_branch`, `track_branch`, and `update_from_base`, naming the affected paths. Untracked files do not block these default preflights; explicit-base rebase and no-commit revert require stricter clean state and refuse ignored residue; Git still refuses non-ignored file collisions, and tracking/base-update/landing operations also protect ignored files. Standalone `integrate_branch` still requires a fully clean control worktree, including untracked files. Standalone `remove_worktree` rejects any staged, unstaged, untracked, or unmerged entry in a linked 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.
|
|
40
40
|
|
|
41
|
-
BranchMe does not
|
|
41
|
+
Outside `commit_changes`, BranchMe does not create user-authored commits or accept commit messages. It does not force checkout/removal, stash, reset, force-push, or expose arbitrary project-file editing. Guarded restoration below is an explicit Git-mediated file mutation. 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, or commit-message control and no generic `continue_merge` or `abort_merge` tool. General index changes are limited to explicitly approved `stage_changes`/`unstage_changes` and no-commit `revert_changes`; `conclude_merge` still adds exactly the formerly unmerged paths after marker checks. `restore_paths` separately permits explicitly authorized replacement/deletion of selected tracked working files while preserving logical index entries; raw Git stat-cache bytes may refresh. 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.
|
|
42
|
+
|
|
43
|
+
## Guarded shell-free operations
|
|
44
|
+
|
|
45
|
+
`git_log`, `git_show`, and `git_conflicts` are explicit read-only history/object/index-stage surfaces. Shared readers disable optional locks, lazy fetch, pagers, and fsmonitor, cap raw responses at 4 MiB, and sanitize before bounding text. History pages pin captured commit IDs; shallow history is explicitly local-only. Merge comparisons require a parent, and binary/oversized blobs are summarized. Conflict previews expose only requested stages; absent versions are explicit. These opt-in content reads never enlarge automatic context and never resolve or stage conflicts. Requested content remains potentially sensitive and untrusted despite best-effort redaction.
|
|
46
|
+
|
|
47
|
+
`stage_changes`/`unstage_changes` require 1–25 exact approved paths and fresh HEAD/logical-index expectations. They refuse active operations, unmerged entries, unsupported/split/sparse/intent-to-add/assume-unchanged/skip-worktree indexes, gitlinks, directory expansion, ignored additions, and Git path environment overrides. Selected file fingerprints are rechecked; working bytes and unrelated index entries are verified unchanged. Both rename sides need approval. No commit, force-add, hunk editing, or all-files mode exists. Git clean filters remain trusted configuration and may execute arbitrary code.
|
|
48
|
+
|
|
49
|
+
`commit_changes` creates local commits only and never pushes. `commit_changes action=gather` is read-only. Drafting uses only the active Pi model, with at most one retry or repair prompt; drafting diagnostics never include raw prompts, diffs, file contents, or model output. Secret files such as `.env`, `.envrc`, private keys, kubeconfigs, and credential stores are not read. Sensitive, generated, binary-looking, unreadable, oversized, and symlinked changed files are omitted from model context. Diff excerpts are collected with external diff and textconv disabled, then redacted. Before staging, commit actions rescan for known secret files, unreadable files, and high-confidence secret tokens. UI sessions must approve flagged files; non-UI sessions refuse them. Commits stop if `git status` changed after context gathering, and `paths` refuses listed paths without changes or staged changes outside the list.
|
|
50
|
+
|
|
51
|
+
`restore_paths` previews selected tracked paths and applies only with explicit discard authorization, fresh HEAD/index expectations, and matching per-file fingerprints. An explicit source must be pinned to a full commit when applying. The index's staged entries stay unchanged (Git may refresh stat-cache bytes), while approved working paths can be replaced or deleted. Untracked/ignored content, symlink ancestors, unsupported index modes, and directory expansion are protected/refused. No reset/clean or automatic destructive rollback exists; a partial failure reports that selected paths may already have changed.
|
|
52
|
+
|
|
53
|
+
`revert_changes` requires one contained non-merge full commit and a clean idle checkout with current expectations, and refuses ignored local-only files at the paths the commit touches (Git could overwrite them); ignored files elsewhere, such as `.env` or installed dependencies, do not block it. It uses `git revert --no-commit` with rerere disabled. Conflicts use owned `REVERT_HEAD` evidence before abort and verified restoration. A successful inverse may leave operation markers; owned state is cleared by `git revert --quit`, preserving the staged inverse without creating a commit. Unknown ownership or uncertain cleanup blocks a success claim. No generic sequencer continuation or hidden commit call exists.
|
|
54
|
+
|
|
55
|
+
`list_remotes` returns bounded sanitized destination previews and a configuration fingerprint, not arbitrary config contents. Add-only `add_remote` requires an unused non-overlapping name, credential-free HTTPS/SSH destination, current config expectation, regular repository config, and no URL rewriting. `set_upstream` changes only one local branch's remote/merge keys for an existing direct remote-tracking ref or explicit unset; inherited/included/ambiguous/worktree-specific settings are refused. Config mutations are common-directory queued and verified, never change global/system config, and never fetch automatically. Failed verification reports uncertain config rather than rolling it back blindly.
|
|
56
|
+
|
|
57
|
+
Mutating tool calls now have an outer process-wide coordinator before the existing helper root/common-directory queues. This serializes tool-mediated mutations across linked worktrees without taking an external filesystem lock. Direct helper callers retain their existing queue contracts. Read-only tools remain independent; callers must sequence dependent reads after mutations. No mechanism guarantees isolation from independent Git processes, extensions, or malicious filesystem writers.
|
|
58
|
+
|
|
59
|
+
See [shell-free workflows](docs/SHELL_FREE_WORKFLOWS.md) for exact contracts, output limits, supported indexes, and recovery guidance.
|
|
60
|
+
|
|
61
|
+
## Explicit diff inspection
|
|
62
|
+
|
|
63
|
+
`git_status` explicitly reads NUL-delimited porcelain v2 status, logical index entries/flags, operation markers, and changed-path filesystem metadata, with optional same-repository `worktreePath`. Its Git calls disable optional locks, lazy fetching, pagers, and fsmonitor; no direct network request or mutation occurs. Normal Git clean filters may still execute during status comparison, so this is not a configuration sandbox. Ignored paths and submodule working-tree contents are excluded, and Git's assume-unchanged/skip-worktree policies still apply. Each raw Git response is limited to 4 MiB; pages contain at most 50 entries / 24 KiB of entry JSON. Whole-checkout changes and filtered results/completeness are distinguished. Continuation hashes bind checkout/filter/HEAD/index/operations/status/available filesystem metadata and reject detected changes, but do not lock out external writers. The logical index fingerprint covers stage entries and `ls-files -v` flags, not raw stat-cache bytes. Explicit `paths` additionally read bounded regular-file bytes or symlink targets solely to produce content/mode fingerprints (8 MiB per regular file); contents are never returned or added to automatic context. Symlink ancestors, unsafe/invalid-encoding, redacted, and abbreviated paths are non-reusable. Filesystem reads verify observed identities before/after but cannot provide isolation from a malicious concurrent filesystem writer. All text/details are sanitized before bounding, and no raw paths are hidden in continuation tokens.
|
|
64
|
+
|
|
65
|
+
`git_diff` is an explicit content-reading surface, separate from metadata-only automatic context and `branch_status`. It runs only discovery, commit resolution, optional `merge-base --all`, and `git diff`; it never stages, commits, writes a diff artifact, or directly changes files/refs. All its Git calls disable optional locks, lazy promisor fetching (`--no-lazy-fetch`, required Git support), and fsmonitor. Diff calls disable external diff helpers, text conversion, color, rename detection, and submodule working-tree scanning; gitlink changes are still shown. Normal Git clean filters may still execute while Git compares working-tree content, so this is not a sandbox against trusted Git configuration or filter side effects.
|
|
66
|
+
|
|
67
|
+
Refs must be bounded single commit revisions, resolved with `rev-parse --verify --end-of-options <revision>^{commit}` before comparison. Raw range syntax and option-like refs are refused; missing/multiple merge bases fail. Path filters are at most 25 literal, root-relative file/directory names of at most 4,096 characters, passed after `--` with `--literal-pathspecs`; absolute paths, parent traversal, `.git` components, backslashes, and controls are rejected. Optional `worktreePath` is verified as a live root of the current repository. The read is not an atomic filesystem snapshot; finish dependent mutations first.
|
|
68
|
+
|
|
69
|
+
Returned content/details are credential-redacted and control-escaped before truncation to 1,000 lines / 32 KiB of diff text, with explicit truncation metadata and a model-facing notice before the diff body. Model-visible results also identify the actual comparison, repository, bounded literal-filter previews, and scope; an empty filtered comparison is not reported as a clean checkout. No unbounded raw diff is retained in details or written to disk. Counts describe the sanitized output before truncation. Binary output is Git's normal summary, not a binary patch; attributes may affect classification. Untracked file contents and submodule working-tree contents are excluded. Redacted/truncated display is not an apply-ready patch. Shared credential patterns and process `GITHUB_TOKEN`/`GH_TOKEN` values are redacted, but arbitrary secrets in tracked files are not guaranteed to be detected. Requested content reaches Pi/the model and must be treated as untrusted data, not instructions; BranchMe makes no GitHub API call for diffs and never adds diffs to automatic context.
|
|
42
70
|
|
|
43
71
|
## Network behavior
|
|
44
72
|
|
|
@@ -53,12 +81,20 @@ GET https://api.github.com/repos/{owner}/{repo}/pulls?state=open&head={owner}:{
|
|
|
53
81
|
GET https://api.github.com/repos/{owner}/{repo}/branches/{headBranch}
|
|
54
82
|
GET https://api.github.com/repos/{owner}/{repo}/branches/{baseBranch}
|
|
55
83
|
POST https://api.github.com/repos/{owner}/{repo}/pulls
|
|
84
|
+
GET https://api.github.com/repos/{owner}/{repo}/commits/{sha}/check-runs?filter=latest&per_page=25&page={page}
|
|
85
|
+
GET https://api.github.com/repos/{owner}/{repo}/commits/{sha}/status?per_page=25&page={page}
|
|
86
|
+
GET https://api.github.com/repos/{owner}/{repo}
|
|
87
|
+
PATCH https://api.github.com/repos/{owner}/{repo}/pulls/{number}
|
|
88
|
+
PUT https://api.github.com/repos/{owner}/{repo}/pulls/{number}/merge
|
|
89
|
+
POST https://api.github.com/graphql
|
|
56
90
|
GET https://api.github.com/repos/{owner}/{repo}/pulls/{number}
|
|
57
91
|
GET https://api.github.com/repos/{owner}/{repo}/pulls?state={open|all}&head={owner}:{branch}&sort=updated&direction=desc&per_page={1|2}
|
|
58
92
|
```
|
|
59
93
|
|
|
60
94
|
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. All of these operations require `GITHUB_TOKEN` or `GH_TOKEN` from the process environment or verified-root `.env` fallback.
|
|
61
95
|
|
|
96
|
+
`pull_request_checks` reads run/status pages and available host mergeability/review/rollup state for an exact head; partial pages, permission failures, missing policy, no checks, and moved heads never imply approval. `edit_pull_request` updates only supplied title/body/draft fields of an open PR, verifies preservation of omitted fields, and reconciles ambiguous writes by reads. `merge_pull_request` requires explicit authorization, expected head SHA, an enabled selected method, known CLEAN/MERGEABLE host state, and no known outstanding review/check blocker. It submits only `sha` and `merge_method`; no admin/bypass, auto-merge, branch deletion, local merge, push, or cleanup is exposed. GitHub enforces protections. The endpoint leases HEAD but cannot atomically lease base SHA: head/base/state are rechecked before the write and merged identities afterward. Lost responses trigger read-only reconciliation, not a second blind merge. New requests reject redirects, bound transport/body to 10 seconds/64 KiB, and use existing repository/token resolution. Readiness metadata is not merge authorization.
|
|
97
|
+
|
|
62
98
|
`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. Optional `kind` selects local or remote-tracking refs; optional `patterns` accepts 1–25 nonblank, control-free Git branch-list glob patterns of at most 512 characters, matched before output limits and display redaction. Patterns are argv values after `--`, never shell code or refspecs. Paths and names are display-safe metadata, not executable handoffs. Upstream counts reflect cached refs, not a fresh remote fetch; call `fetch_remote` separately when fresh discovery is needed.
|
|
63
99
|
|
|
64
100
|
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.
|
|
@@ -71,16 +107,16 @@ The branch preflight requests have no body. BranchMe uses the resolved `headBran
|
|
|
71
107
|
|
|
72
108
|
- `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.
|
|
73
109
|
- The GitHub repository is inferred from local `origin` and/or `GITHUB_REPOSITORY`.
|
|
74
|
-
-
|
|
110
|
+
- `git_status` accepts literal root-relative status filters and an optional verified same-repository worktree selector; it never stages or changes files/refs. `git_diff` accepts literal root-relative content filters and an optional verified same-repository `worktreePath`; `branch_status`, `integrate_branch`, and `conclude_merge` also accept that worktree selector. History/conflict and guarded index/restore/revert tools support the same selector. Branch-dependent create/change/track/fetch/pull/rebase/push/base-update/PR tools also support it; branch-inferred PR status/feedback reject it with an exact number. Initialization still accepts no path. Literal content/index paths are repository-root-relative, not arbitrary filesystem targets. 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.
|
|
75
111
|
- `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.
|
|
76
112
|
- `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.
|
|
77
113
|
- `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.
|
|
78
114
|
- `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.
|
|
79
|
-
- `change_branch` accepts
|
|
115
|
+
- `change_branch` accepts `branchName` and an optional verified worktree selector and never creates branches, checks out remote branches, forces, stashes, or discards changes.
|
|
80
116
|
- `fetch_remote` accepts only optional configured `remote` and boolean `prune`, not URLs, arbitrary refspecs, force, tag, or checkout controls. Only explicit `prune: true` deletes stale remote-tracking branch refs; local branches, tags, and other remotes remain untouched.
|
|
81
117
|
- `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.
|
|
82
|
-
- `pull_branch` accepts
|
|
83
|
-
- `rebase_branch` accepts
|
|
118
|
+
- `pull_branch` accepts only an optional worktree selector, updates only the clean current branch from its configured upstream, and uses fast-forward-only semantics.
|
|
119
|
+
- `rebase_branch` accepts optional `baseRef` and a verified worktree selector, rebases the selected clean branch onto a configured upstream or captured explicit local base, disables autostash and multi-ref updates, never pushes, and attempts to abort on failure.
|
|
84
120
|
- `integrate_branch` requires exactly `sourceBranch` and `targetBranch`, plus optional boolean `keepConflicts` (default `false`). 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.
|
|
85
121
|
- 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.
|
|
86
122
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, PR creation and related-PR lookup fail closed.
|