@senad-d/branchme

Pi extension for verified Git repository initialization, branch, worktree, integration, retirement, push, and pull request workflows.

Packages

Package details

extension

Install @senad-d/branchme from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@senad-d/branchme
Package
@senad-d/branchme
Version
0.3.12
Published
Oct 15, 2026
Downloads
1,164/mo · 508/wk
Author
senad-d
License
MIT
Types
extension
Size
1.4 MB
Dependencies
0 dependencies · 4 peers
Pi manifest JSON
{
  "image": "https://raw.githubusercontent.com/senad-d/BranchMe/main/img/demo.gif",
  "extensions": [
    "./src/extension.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README


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 twenty-five agent-callable tools that initialize a repository, refresh state, 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.

  • Context-aware: every agent run starts with bounded branch, working-tree, related-PR, and recent-commit metadata; repository metadata is untrusted data, not instructions.
  • 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.
  • Explicit history rewrites: rebase_branch runs only when explicitly requested, requires a clean current branch with an upstream, disables autostash and multi-ref updates, and automatically attempts to abort on failure.
  • 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.
  • Commit-safe: context collection is read-only, and BranchMe never creates user-authored commits, accepts or generates commit messages, force-pushes, resets, or edits files directly. Explicit integrate_branch and update_from_base calls may let Git create a standard merge commit for divergent histories; conclude_merge stages only the formerly unmerged paths of a kept merge and commits them with Git's prepared merge message.
  • Strict tools: tool schemas reject undocumented properties such as stash, discard, owner, repo, or path; baseRef is supported only by create_worktree in new-branch mode; the only force decision is the required boolean on retire_branch, and every tool accepts only its documented fields.
  • 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.

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.

Table of Contents


Quick Start

pi install npm:@senad-d/branchme
cd /path/to/your/git/repo
pi

Inside pi:

/branchme
/branchme help

A normal prompt can use the automatic start-of-run snapshot without a tool call. Ask for an explicit refresh when needed, for example:

Refresh the repository state with branch_status, then create a branch named feature/update-docs with create_branch.

When pi starts in a directory that is not already inside a Git repository, explicitly ask it to call init_repository. The tool defaults to an unborn main branch, or accepts an optional initialBranch; it creates no commit, README, .gitignore, remote, or user configuration.

Use list_branches to discover local and cached remote-tracking branches, upstream ahead/behind counts, and worktree occupancy. Optional kind and patterns filter before the 200-entry limit. For fresh discovery of unknown branch names, first run fetch_remote; fetch_branch remains the narrow refresh for a known branch. To join an existing remote branch in the active checkout, use track_branch rather than creating from the wrong HEAD.

For example, replace git fetch origin --prune followed by git branch -r --list 'origin/feat/23' 'origin/feat/23-*' with these sequential calls:

fetch_remote({ remote: "origin", prune: true })
list_branches({ kind: "remote-tracking", patterns: ["origin/feat/23", "origin/feat/23-*"] })

Wait for the fetch to succeed before listing. fetch_remote defaults to origin and does not prune unless prune: true is requested. Pruning removes only stale cached branch refs for that remote, never local branches, tags, other remotes, or branches on the server. It fetches remote heads using an internal refspec, ignoring configured fetch mappings and tag-pruning settings. Dirty and detached checkouts are supported without changing files, HEAD, or upstream configuration. Rewritten remote tips are refreshed too. Conventional same-remote HEAD aliases are preserved; the remote branch literally named HEAD is excluded to prevent alias writes. Unsafe symbolic destinations (including dangling aliases), overlapping configured remote namespaces (such as origin and origin/team), and non-files ref backends such as reftable are refused before fetching.

A typical BranchMe flow is:

  1. Use the automatic snapshot to understand state at the start of the agent run.
  2. Use branch_status when you explicitly need fresh state, especially after a Git mutation in the same run.
  3. Switch to the local base branch with change_branch.
  4. Update it from its configured upstream with pull_branch, which requires a clean worktree and uses fast-forward-only semantics.
  5. Create from the updated HEAD with create_branch.
  6. Make edits and commit outside BranchMe.
  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.
  8. Push the current branch with push_branch.
  9. After push_branch completes and GitHub can see the branches, create or reuse a matching open pull request with pull_request.
  10. Inspect the PR with pull_request_status({ number: 123 }). Merge it on GitHub outside BranchMe, then use land_branch from the primary checkout. Supply pullRequestNumber: 123 for squash/rebase merge evidence.

For isolated work, a specialized Git subagent can use the explicit worktree workflow:

  1. Call list_worktrees to inspect the current repository's bounded worktree inventory.
  2. Ask the user to provide or approve an exact absolute destination and call create_worktree with branchMode: "new" or "existing".
  3. Wait for the result and require details.handoff.ready === true.
  4. Have the caller or orchestrator start a separate Pi session or subagent with its working directory set to the returned absolute details.handoff.cwd.
  5. After that session finishes, remove or preserve any staged, unstaged, untracked, or unmerged files, then explicitly call remove_worktree if removal was requested. Ignored residue is protected by default; after preserving anything valuable, explicitly set deleteIgnored: true to delete it with the worktree. Removal retains the local branch.
  6. If the user separately asks to retire that retained branch, obtain its fresh exact HEAD, verify it against an exact local target, and call retire_branch only after worktree removal has completed.

After a pull request merges on the host, use land_branch from the repository root instead of composing fetch/removal/retirement/sync calls. This combined workflow deletes ignored worktree residue and retires the source branch. Standalone remove_worktree always retains the branch and protects ignored residue unless deleteIgnored: true explicitly authorizes its deletion.

BranchMe does not change the active Pi process's cwd, start Pi or other processes, create sessions, or copy .env or other ignored/untracked files. For credentials needed by agents in linked worktrees, prefer process-level environment variables rather than copying repository-root secrets.

To refresh the current branch's configured remote-tracking ref without changing the local branch or working tree, use fetch_branch with no arguments. To refresh another remote-tracking ref — for example origin/main after a pull request merged on GitHub — call fetch_branch with branch (and optional remote, default origin); the targeted fetch never touches local branches, the working tree, or the current branch's upstream configuration. To reconcile the clean current branch by rewriting its local commits, run fetch_branch, wait for it to complete, and then run rebase_branch. The no-argument fetch_branch and rebase_branch require a configured upstream; rebase_branch automatically attempts git rebase --abort if rebasing fails.

BranchMe is tool-based. The slash command is informational only and never initializes repositories, changes or updates branches, creates or removes worktrees, changes cwd, starts processes/sessions, fetches, rebases, pushes, commits, stages, edits files, or opens pull requests.


Installation

Scope Command Notes
Global pi install npm:@senad-d/branchme Loads in every trusted pi project.
Project-local pi install npm:@senad-d/branchme -l Writes to .pi/settings.json in the current project.
One run pi -e npm:@senad-d/branchme Try without changing settings.
Git pi install git:github.com/senad-d/branchme@<tag> Pin a tag or commit.
Local checkout pi --no-extensions -e . Develop or test this repository in isolation.

Once loaded, BranchMe collects automatic Git context before each agent run. This happens for ordinary prompts; there is no /branchme context command and no additional tool to enable it.

Source checkout:

git clone https://github.com/senad-d/branchme.git
cd branchme
npm install --ignore-scripts
npm run validate
pi --no-extensions -e .

Use the checkout globally while developing:

pi install /absolute/path/to/branchme

Repository and GitHub Setup

BranchMe does not bundle Git. Start pi inside an existing repository, or start it in an existing non-repository directory and explicitly request init_repository:

cd /path/to/your/git/repo
git status
git remote get-url origin
pi

For a new repository:

mkdir my-project
cd my-project
pi
# Ask: Initialize this directory with init_repository.

Initialization rejects existing or nested repositories, defaults to an unborn main branch, and creates no commit or project files. Git environment overrides that redirect repository paths or discovery must be unset. Failed or unrecognized repository discovery is not treated as permission to initialize; uncertain postconditions require inspection without automatic cleanup.

For pull requests, the repository must resolve to GitHub from local origin and/or GITHUB_REPOSITORY:

git remote set-url origin git@github.com:OWNER/REPO.git
# or
export GITHUB_REPOSITORY=OWNER/REPO

For automatic related-PR lookup, pull_request_status, pull_request_feedback, PR-aware land_branch, and pull_request, set a token in the process environment before starting pi:

export GITHUB_TOKEN=github_pat_...
# or
export GH_TOKEN=ghp_...
pi

Or copy .env.example to .env in the repository root, fill in one token value, and optionally enable PR field autofill:

cp .env.example .env
$EDITOR .env
# Set BRANCHME_PR_AUTOFILL=true in .env if desired.
pi

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 the locally available configured upstream ref and makes no network request itself. 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. 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.


Configuration

BranchMe has no separate project config file. It reads process environment variables and supported keys from a local .env file in the verified git root. Token lookup checks process.env.GITHUB_TOKEN, then process.env.GH_TOKEN; if neither is set, BranchMe checks the matching .env keys. Pull request field autofill checks BRANCHME_PR_AUTOFILL in the process environment first, then .env, and defaults to disabled.

Variable Meaning
GITHUB_TOKEN Preferred token for automatic related-PR lookup, pull_request_status, pull_request_feedback, PR-aware land_branch, and pull_request; process environment first, then local .env fallback.
GH_TOKEN Fallback token for the same GitHub API operations; process environment first, then local .env fallback.
BRANCHME_PR_AUTOFILL=true Allow pull_request to fill omitted PR fields. Accepts true/false, 1/0, yes/no, or on/off; disabled by default.
GITHUB_REPOSITORY=owner/repo Optional CI fallback and boundary check for the current GitHub repository; process environment only.

BranchMe reads only GITHUB_TOKEN, GH_TOKEN, and BRANCHME_PR_AUTOFILL from a small regular .env file; it rejects directories, symlinks, special files, and oversized files. BranchMe does not import other .env keys, read shell profiles, GitHub CLI credentials, or local credential stores. Token values are redacted from automatic context, errors, tool content, and tool details.

With autofill enabled, omitted fields are resolved as follows:

  • headBranch: current local branch.
  • baseBranch: the branch named by origin/HEAD when it exists locally, falling back to an existing local main, master, trunk, or develop branch.
  • title: first commit subject in baseBranch..headBranch, falling back to a title derived from the head branch name.
  • body: a bounded Markdown summary of commit subjects in baseBranch..headBranch.
  • draft: false.

Explicit tool arguments always take precedence. Autofill does not invoke pull_request by itself. Agents may invoke pull_request without separate end-user confirmation when PR creation is appropriate for the active workflow, including directions from user prompts, system or developer instructions, AGENTS.md, skills, automation, or delegated/subagent prompts.

If local origin and GITHUB_REPOSITORY both resolve but disagree, pull_request fails closed.


Commands

Command Description
/branchme Show a compact status panel or fallback message with current branch, GitHub repository resolution, token presence, and workflow notes.
/branchme help Show concise BranchMe workflow help and runtime requirements.
/branchme --help Alias for /branchme help.
/branchme -h Alias for /branchme help.

Commands are informational only. BranchMe actions are performed by agent-callable tools. /branchme uses the TUI panel in TUI mode, notifications in RPC mode, plain text only in print mode, and stays stdout-silent in JSON mode to avoid corrupting protocol output. Unsupported non-empty /branchme arguments return guidance to use /branchme help where the current mode can display it safely.


Tools

Tool Schema Behavior
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.
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.
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.
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.
conclude_merge { "action": "conclude" | "abort" } Finish or abandon the in-progress merge on the current checkout. conclude refuses default/custom-sized <<<<<<<, `
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.
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.
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.
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.
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.
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.
branch_status { "ancestry"?: { "sourceBranch": string, "targetBranch": string } } Explicitly refreshes the same bounded context used at agent start. An optional strict ancestry query captures both exact HEADs and reports whether the source commit is an ancestor of the target commit; each endpoint may be an exact local branch or a remote-tracking ref such as origin/main (a local branch of the same name takes precedence). It is read-only and never checks out or resets remote-tracking refs; automatic Git context does not run ancestry queries.
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.
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.
remove_worktree { "worktreePath": string, "deleteIgnored"?: boolean, "discardChanges"?: boolean } Force-free removal of an explicitly selected, verified clean linked worktree. Ignored residue is refused by default; deleteIgnored: true explicitly deletes it and reports its top-level paths. The local branch remains at the same commit. discardChanges: true explicitly discards uncommitted changes and lists them in discardedPaths; a dirty worktree is refused without it.
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.
land_branch { "sourceBranch": string, "targetBranch": string, "remote"?: string, "worktreePath"?: string, "pullRequestNumber"?: integer } Post-merge fetch, ancestry or exact merged-PR proof, linked-worktree removal including ignored residue, leased local source deletion, and independent fast-forward target sync. Default remote origin; omitted path finds the source's linked worktree. Returns per-step receipts.
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.
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.
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.
rebase_branch {} Requires a clean current branch with a configured upstream and runs git rebase --no-autostash --no-update-refs <upstream>; it rewrites local commits and automatically attempts git rebase --abort on failure.
integrate_branch { "sourceBranch": string, "targetBranch": 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. It never fetches or pushes.
create_branch { "branchName": string } Validates branchName, rejects existing local branches, and runs git switch -c <branchName> from current HEAD.
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.
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.

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.

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 and rebase_branch have strict empty schemas and never accept a branch, remote, refspec, force, autostash, or arbitrary rebase target. integrate_branch requires exactly sourceBranch and targetBranch; it accepts no repository, path, remote, strategy, message, squash, signing, commit, continuation, abort, force, fetch, push, deletion, or worktree controls. 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.


Workflow and Boundaries

PR retries and lifecycle inspection

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.

pull_request_status reads open, closed-unmerged, and merged PRs with a 10-second deadline per API request and a 64 KiB response limit. A successful empty branch lookup returns pullRequest: null; authentication, network, or malformed-response failures are errors rather than "no PR". Exact-number lookup is preferred for cleanup evidence. Fork PRs, PR edits/merges, review and required-check policy, clone, remote configuration/deletion, and explicit first-push destination selection remain outside this implementation.

Automatic context and freshness

Before each agent run, BranchMe appends an Automatic Git Context snapshot to the existing system prompt. The snapshot contains these fields in order:

  • current branch or detached HEAD, plus upstream and ahead/behind counts when available;
  • working-tree state and staged, unstaged, and untracked counts;
  • up to 20 unstaged or untracked change entries with Git status, path, and original path for renames/copies;
  • related open PR status and, when found, its number, title, repository, head/base branches, URL, state, and draft flag;
  • whether pull request field autofill is enabled;
  • up to 5 recent commits with short hash, date, and subject.

Collection defaults are a 5-second timeout per local Git command, a 4-second related-PR lookup timeout, at most 512 characters per metadata value, and at most 4,000 characters for the rendered snapshot. GitHub response bodies are limited to 64 KiB. The formatter can further shorten values or omit entries to stay within the total limit.

The snapshot is fresh at agent start but is not live. A fetch, branch switch, pull, rebase, integration, commit, file change, push, or other mutation later in the same run can make it stale. branch_status performs an explicit current-state refresh through the same shared collector and remains read-only; it does not mutate files, Git state, or GitHub state. Its optional ancestry object requires exact sourceBranch and targetBranch fields together, captures both commit IDs, and reports isAncestor. Each endpoint may be an exact local branch or a remote-tracking ref such as origin/main (local branches take precedence), so a fetched origin/main can serve as a read-only containment proof after a pull request merges. This targeted proof is explicit only: automatic Git context remains unchanged. Run it after integrate_branch or fetch_branch completes, never in the same parallel tool batch.

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.

Repository paths, commit subjects, branch names, and PR titles are treated as untrusted metadata. BranchMe redacts token values, escapes control characters, quotes and bounds values before prompt insertion, and never captures diffs or file contents. Staged files contribute only to the staged count; the path list is limited to unstaged and untracked entries.

Use BranchMe from pi prompts or automation that drives pi with explicit tool calls:

Use branch_status.
Switch to the base branch with change_branch, then use pull_branch after the switch completes.
Fetch the current branch upstream with fetch_branch, wait for it to complete, then rebase with rebase_branch.
Create branch feature/docs-refresh from the updated current HEAD with create_branch.
Push the current branch with push_branch.
After push_branch completes, create a draft pull request from feature/docs-refresh to main titled "Refresh docs" with this body: "...".
If pull request field autofill is enabled, after push_branch completes create a pull request and fill any details I did not provide.

Verified local branch integration

integrate_branch requires explicit intent and exactly two distinct existing local branch names: sourceBranch and targetBranch. The active Pi worktree is the control worktree; it must be clean, have no merge/rebase/cherry-pick/revert/sequencer operation in progress, and already have targetBranch checked out. BranchMe never switches to the target or infers one. A source checked out in another dirty linked worktree is allowed because integration reads only its committed local ref. Remote-only refs, commit IDs, paths, repositories, remotes, and owner-prefixed refs are rejected.

For a source not already reachable from the target, BranchMe runs this fixed normal-merge policy from the verified worktree root:

git -c rerere.enabled=false merge --ff --no-edit --no-autostash --no-rerere-autoupdate --no-overwrite-ignore refs/heads/<sourceBranch>

This policy permits a fast-forward or lets Git create a standard two-parent merge commit for divergent histories. BranchMe 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.

The structured status is one of:

  • already_integrated: no merge ran and both branch refs remained stable;
  • fast_forward: the target advanced exactly to the captured source commit;
  • merge_commit: Git created an exact normal two-parent merge whose parents are the captured prior target and source;
  • conflict: bounded, exact repository-relative conflict paths were captured, git merge --abort succeeded, and repository identity, current target, exact source/target refs, clean state, and absence of operation state were verified.

All outcomes include captured before/after source and target commit IDs plus final source and prior-target ancestry proof. Failed non-conflict merges remain errors after cleanup. BranchMe uses no reset-based rollback; if abort or postcondition checks are inconclusive, or a ref moves unexpectedly, it reports that integration may have completed and tells the caller to inspect the repository before retrying.

The same-repository mutation queue covers preflight, merge, cleanup, and verification, but it exists only inside the current BranchMe process. It does not lock another Pi session or an external Git process. integrate_branch itself does not fetch, pull, push, delete branches, remove worktrees, start agents, ask questions, continue a merge, or resolve semantic conflicts. A Mission or other orchestrator may explicitly call it; after a verified conflict, that separate workflow decides whether to delegate analysis to an integration agent or ask the developer about semantic intent.

For an independent read-only proof after integration, call:

{ "ancestry": { "sourceBranch": "feature/example", "targetBranch": "main" } }

with branch_status only after integrate_branch has returned. Do not batch the calls.

Linked worktree verification and handoff

create_worktree requires a non-blank absolute path with no control characters. Missing parent directories are allowed: BranchMe resolves the nearest existing directory ancestor to build a canonical destination, rejecting non-directory or dangling-symlink ancestors. Git creates missing directories only after all preflight checks pass. BranchMe rejects any existing file, directory, or symlink there, and rejects destinations inside a registered worktree or the repository's common Git directory. Before mutation, the canonical path and local branch must be returnable without redaction, escaping, Unicode alteration, or truncation: canonical paths are limited to 4,096 characters and branch identities to 512 characters, and credential-like token text is rejected. A dirty source worktree is allowed because creation does not switch or overwrite it.

For branchMode: "new", BranchMe creates the requested local branch from the current HEAD, or — when the optional baseRef is supplied — from that exact existing local branch, remote-tracking ref (for example origin/main), or full commit. baseRef is resolved to a commit before mutation and is only read: it is never checked out, reset, or given upstream configuration, and the current checkout's branch, dirt, or staleness never blocks it. For branchMode: "existing", it uses only an existing local branch that is not checked out in another worktree and rejects baseRef; it never infers a local branch from a remote. After Git succeeds, BranchMe re-lists worktrees and verifies the canonical path, local branch, HEAD, and clean checkout. A representative structured result subset is:

{
  "action": "create_worktree",
  "handoff": {
    "cwd": "/absolute/path/to/branchme-feature",
    "branch": "feature/worktree-docs",
    "head": "<full-commit-id>",
    "ready": true,
    "summary": "Worktree ready at /absolute/path/to/branchme-feature on branch feature/worktree-docs at <full-commit-id>."
  }
}

The full details also distinguish requested input from verified before/after state. Successful handoff.cwd and handoff.branch values are the exact identities verified against Git; BranchMe never substitutes [REDACTED], escaped control sequences, or a truncation ellipsis in these machine-readable fields. Display content, summaries, and worktree inventory remain sanitized and bounded separately. An orchestrator may use handoff.cwd only after ready is true, and must start the next Pi session or subagent itself with that exact working directory. BranchMe never changes the active process's cwd or starts another process/session.

remove_worktree canonicalizes the approved absolute path and requires an exact match in a fresh inventory for the current repository. It accepts only a present, unlocked, non-prunable, non-bare, branch-attached linked worktree that is neither main nor current, then rejects staged, unstaged, untracked, or unmerged entries. Ignored entries are refused by default without disclosing their paths. Optional deleteIgnored: true explicitly authorizes deleting all ignored files and directories with the worktree; preserve .env, .pi/, dependency caches, build output, or other valuable residue first:

{
  "worktreePath": "/absolute/path/to/branchme-feature",
  "deleteIgnored": true
}

The bounded receipt reports only redacted top-level ignored paths in deletedIgnoredPaths, never contents. The canonical path and retained branch must pass the same pre-mutation lossless-identity checks used for creation. Removal uses git worktree remove <verified-path> without force, verifies the entry is gone, and returns the exact retained branch identity after confirming it still points to the captured commit:

{
  "action": "remove_worktree",
  "deletedIgnoredPaths": [],
  "handoff": {
    "cwd": null,
    "branch": "feature/worktree-docs",
    "head": "<full-commit-id>",
    "ready": false,
    "summary": "Worktree directory /absolute/path/to/branchme-feature was removed; local branch feature/worktree-docs was retained at <full-commit-id>."
  }
}

Ignored and untracked files—including a repository-root .env—are not copied into a new linked worktree. Prefer credentials inherited through the new agent process environment. Git documents support for multiple worktrees of a superproject containing submodules as incomplete; BranchMe adds no force-based submodule cleanup. There is no force, move, prune, repair, lock, unlock, detached, orphan, or remote-inference worktree behavior.

Post-merge cleanup in one call

After the pull request merged on the host, run from the repository root:

{
  "sourceBranch": "docs/74-add-canonical-glossary-context",
  "targetBranch": "main",
  "remote": "origin",
  "worktreePath": "/absolute/path/to/issue-74-worktree"
}

remote defaults to origin. Omit worktreePath to find the linked worktree holding sourceBranch, if any. An explicit path can also select a worktree incorrectly parked on targetBranch; unrelated branches, primary/root checkouts, detached, locked, dirty, or foreign worktrees are not removed. Invocation from inside the removal directory (including a nested or symlinked cwd) is refused: run from the repository root. Other caller directories in the same repository are supported; every Git command has an explicit -C directory, and cleanup runs through the primary checkout. When the merged source branch is checked out in the primary checkout and its tracked files are clean, land_branch switches that checkout to the target branch first (checkout: switched) so the source can be retired; untracked files stay in place.

The tool fetches the exact remote target and by default requires the captured source tip to be its ancestor before cleanup. For squash/rebase merges, supply pullRequestNumber. BranchMe verifies a closed, merged PR in the same GitHub repository as the configured landing remote, exact head/base branch names, a source SHA equal to the PR head or one of its ancestors (a local branch left behind holds nothing the PR lacks; one with commits the PR head does not contain is refused), and containment of the reported merge commit in the fetched target. Supplying a PR number requires valid evidence even when ordinary ancestry succeeds. There is no general force escape hatch. This host-merge proof authorizes retiring the original rewritten source history; those original commits may eventually become unreachable. An existing selected source checkout must be at the proven PR head or at that contained source SHA; a checkout parked on the target still needs target ancestry. Tracked/staged changes and non-ignored untracked files block removal. Ignored .env, .pi/, node_modules/, dist/, and other ignored residue are deleted with the worktree; preserve anything needed first. Only their top-level path entries, never contents, appear in deletedIgnoredPaths. Source retirement uses the captured expected-HEAD lease and the remote-tracking target, not local HEAD or a stale local target.

Target sync is last, even when worktree removal or branch retirement is refused:

Mode Behavior
fetch-refspec Target is not checked out anywhere: non-forced fetch <remote> refs/heads/<target>:refs/heads/<target>. Git enforces fast-forward-only updates.
pull-ff Target is checked out clean: explicit pull --ff-only --no-rebase --no-autostash <remote> refs/heads/<target> in that worktree.
skipped-dirty Target checkout has tracked changes: leave it untouched and report its path and ahead/behind counts against the captured remote target.
noop Local target already equals the captured remote head; no sync mutation.
not-run / failed Initial safety/ancestry refusal or sync failure; a successful fast-forward is never inferred from Git's prose.

The structured result contains repositoryRoot, remote, targetBranch, remoteTargetHead, sourceBranch, sourceHead, ancestry.isAncestor, mergeProof, pullRequest, worktree, branch, targetSync, and ordered steps, plus a one-line summary. Worktree outcomes are removed/absent/refused; branch outcomes are deleted/absent/refused. Full target before/after IDs are read from the exact local ref in this call. Unknown/not-applicable identities and ancestry are null, not invented proofs. A second successful call reports absent cleanup and noop sync. An already-missing directory is reported absent without pruning a remaining registration; such occupancy can still block branch deletion.

land_branch never stashes, resets, checks out/switches branches, force-updates, pushes, prunes, or deletes remote/tracking refs. It holds one process-local primary-root mutation queue; it cannot lock other Pi/external Git processes. Inspect each receipt outcome before declaring landing complete. Standalone remove_worktree and retire_branch retain their original contracts.

Verified local branch retirement

retire_branch is a separate, explicit lifecycle step after integration and any linked-worktree removal. It accepts exactly:

{
  "branchName": "feature/example",
  "expectedHead": "<full-40-or-64-hex-commit-id>",
  "targetBranch": "main",
  "force": false
}

Obtain or supply a fresh exact HEAD for branchName and verify its ancestry against the exact local targetBranch; branch_status.ancestry can provide both captured commit IDs and the read-only proof. Run that refresh and retire_branch sequentially, never in the same parallel tool batch. The retiring and target names must be distinct direct local refs. Any registered worktree that names the retiring branch blocks deletion, including main, current, linked, locked, prunable, or missing records. Removing an occupying linked worktree is a separate explicit remove_worktree call; wait for its verified result before retirement.

Merged retirement uses force: false. If the retiring commit is not an ancestor of the captured target, retirement fails unless the user explicitly authorizes unmerged data loss with force: true. Forced unmerged retirement can remove that commit's last local branch reference. Object recovery is not guaranteed: normal reflog expiry and Git garbage collection can eventually make the commit unreachable.

After validating the expected commit, complete worktree inventory, target ancestry, and repository identity, BranchMe deletes only the exact local ref with an expected-old-value lease:

git update-ref --no-deref -d refs/heads/<branchName> <capturedRetiringHead>

The captured commit lease prevents deletion if the retiring ref moves after preflight. BranchMe does not use git branch -d or git branch -D because those commands do not provide this expected-HEAD lease and their default merge target does not satisfy the explicit targetBranch contract. Retirement never performs bulk or pattern deletion, infers a target, removes a worktree, fetches, pushes, deletes a remote branch or local remote-tracking ref, or recreates/resets a ref as rollback. Local branch.<branchName>.* configuration remains untouched in the initial contract; it can affect a later branch recreated with the same name.

The retirement preflight, immediate reinspection, leased deletion, and postcondition verification share a process-local mutation-queue window keyed by the canonical active worktree root. That queue coordinates BranchMe mutations invoked from the same active checkout, but it does not lock another active worktree, Pi process, or external Git process. The expected-old-value lease protects the retiring ref; target and worktree checks detect other observable movement. After deletion is attempted, BranchMe verifies repository identity, target stability, local-ref absence, and zero occupancy without using a cancelled caller signal. Contradictory or inconclusive outcomes produce a bounded uncertain error stating retirement may have completed; inspect the repository, target ref, retiring ref, and complete worktree inventory manually before retrying. BranchMe does not automatically recreate, reset, switch, merge, or force-update a branch as rollback.

Repository-configured reference-transaction hooks can run during git update-ref. Such hooks are inside the repository trust boundary and can execute arbitrary commands or contact networks; that behavior is distinct from BranchMe's direct local-only, no-remote-delete argv contract.

BranchMe operates only on the repository where pi is running:

  • Automatic collection and branch_status run bounded, read-only Git commands from the verified git root.
  • Git commands use pi.exec("git", args, { cwd, signal, timeout }) with argv arrays; repository mutations run from the verified git root.
  • change_branch switches only to existing local branches and has no force, stash, discard, remote, or path input.
  • 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.
  • 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.
  • rebase_branch requires a clean worktree and configured upstream, rewrites only the current branch onto the locally available upstream with autostash and multi-ref updates disabled, and automatically attempts to abort on failure.
  • 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.
  • create_branch creates from the current HEAD only and has no baseRef input.
  • list_worktrees is an explicit, read-only repository inventory; it is intentionally absent from automatic active-worktree context.
  • 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.
  • remove_worktree passes Git only a freshly verified canonical linked-worktree path, never uses force, and retains the branch.
  • retire_branch deletes only one exact unoccupied direct local ref with the supplied expected-HEAD lease after captured target ancestry verification; it leaves remote refs, remote-tracking refs, worktrees, and branch configuration untouched.
  • push_branch pushes only the current branch, uses no bare upstream git push, and has no branchName input.
  • 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.
  • If local origin and GITHUB_REPOSITORY both resolve but disagree, pull_request fails closed.

BranchMe intentionally does not create user-authored commits, accept or generate commit messages, force checkout, stash changes, discard changes, force-push, reset, edit files directly, copy ignored/untracked files between worktrees, or delete branches during standalone remove_worktree. The only staging it performs is conclude_merge adding exactly the formerly unmerged paths of an in-progress merge. 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.


GitHub Actions

GitHub Actions example:

name: branchme-smoke
on:
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  branchme:
    runs-on: ubuntu-latest
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_REPOSITORY: ${{ github.repository }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm install -g --ignore-scripts @earendil-works/pi-coding-agent
      - run: pi --no-extensions -e npm:@senad-d/branchme --help

Ensure the token and Git credentials have permission for the branch and pull request operation you ask BranchMe to perform.


Troubleshooting

Problem Try
Not a git repository Start pi from inside a git checkout, or explicitly request init_repository for a new project directory.
Detached HEAD Use change_branch to switch to an existing local branch, or checkout a branch before fetch_branch, pull_branch, rebase_branch, create_branch, or push_branch.
Branch already exists Choose a new local branch name for create_branch, or use change_branch to switch to it.
Branch does not exist locally Use track_branch for an existing remote branch; change_branch and create_worktree existing mode still require local branches.
Worktree destination rejected Provide an exact absolute path with a resolvable directory ancestor (missing parents are allowed); the destination must not exist or be inside another registered worktree or the repository's common Git directory. Its canonical path and branch identity must also fit the documented limits without credential-like token text or characters that require escaping.
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.
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.
Linked-worktree agent cannot find credentials Pass credentials through the process environment. BranchMe does not copy repository-root .env or other ignored/untracked files.
Dirty worktree before branch switch, pull, rebase, or integration Commit, stash, or discard changes outside BranchMe before using change_branch, pull_branch, rebase_branch, or a target control worktree for integrate_branch.
Fetch, pull, or rebase has no upstream Configure the current branch upstream outside BranchMe, then retry the tool.
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.
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.
Rebase fails or conflicts rebase_branch automatically attempts git rebase --abort. Inspect repository state before continuing if automatic cleanup also fails.
Integration target mismatch or missing local branch Check out the exact local targetBranch in the active control worktree and ensure both distinct branch refs already exist locally; integrate_branch never switches, fetches, or accepts remote-only refs.
Integration rejects branch-specific merge options Clear branch.<targetBranch>.mergeOptions outside BranchMe before retrying; options such as --no-commit, --squash, --no-verify, or a custom strategy would change the fixed policy.
Integration reports conflict The initial merge was automatically aborted and exact restoration was verified; the conflict paths are listed in the result text. BranchMe has no generic continue_merge tool and does not resolve semantic conflicts.
update_from_base reports conflict_kept The merge is in progress with MERGE_HEAD set and the listed paths unmerged. Have a file-editing step remove every conflict marker, then run conclude_merge({ action: "conclude" }); run conclude_merge({ action: "abort" }) to restore the branch instead. Other BranchMe mutations refuse while the merge is in progress.
conclude_merge refuses with marker paths An unmerged working-tree file or staged blob contains a default/custom-sized conflict marker line. Remove the markers and restage any manually staged resolution; marker-like Markdown underlines also count. Retry or abort.
Integration is uncertain or cleanup fails Inspect branch refs, HEAD, worktree status, and Git operation state manually before retrying. BranchMe does not use reset-based rollback.
Retirement expected HEAD is stale Refresh the exact local branch commit and target ancestry, confirm the intended commit, then make a new sequential retire_branch call. Never reuse a stale commit identity.
Retirement branch is occupied Remove the occupying linked worktree separately only after its own safety checks pass. Current, main, locked, prunable/missing, and any other registered occupancy block retirement.
Retirement rejects unmerged history Preserve or integrate the commits, or obtain explicit user authorization for the data-loss risk before a separate call with force: true.
Retirement outcome is uncertain Retirement may have completed. Manually inspect the repository identity, target ref, retiring local ref, and complete worktree inventory before retrying; do not recreate or reset a branch as automatic rollback.
Push fails Confirm the current branch is correct and your normal Git remote credentials can push.
Related PR is unavailable Set GITHUB_TOKEN or GH_TOKEN before starting pi if related-PR context is wanted. Without a token, BranchMe keeps local context and intentionally makes no unauthenticated GitHub request.
PR auth fails Set GITHUB_TOKEN or GH_TOKEN before starting pi, or copy .env.example to .env and fill in one token.
Missing PR fields Provide all five fields, or set BRANCHME_PR_AUTOFILL=true in the process environment or repository .env.
Autofill cannot infer the base Ensure origin/HEAD names an existing local branch, keep a local main, master, trunk, or develop branch, or provide baseBranch explicitly.
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.
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.
Repository mismatch Make origin and GITHUB_REPOSITORY refer to the same owner/repo.
Need a user-authored commit Use CommitMe or normal Git commands. BranchMe does not accept commit messages or create user-authored commits; integrate_branch, update_from_base, and conclude_merge may let Git create a standard merge commit.
Other extensions interfere Test with pi --no-extensions -e ..

Diagnostics

From a source checkout:

npm run validate
npm run check:pack
printf '/branchme help\n/quit\n' | pi --no-extensions -e .

Validation covers TypeScript typechecking, formatting checks, automatic context collection and prompt injection, mocked GitHub lookup, isolated real-Git worktree, branch-integration, and leased branch-retirement lifecycle tests, package checks, checkout Pi runtime smoke, and package-content verification. The checkout smoke loads BranchMe through Pi, then uses a temporary verifier command to confirm all twenty-five BranchMe tools are 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, and TUI/help captures are stored in docs/TUI_CAPTURE.md.

Refresh TUI captures intentionally with:

UPDATE_TUI_CAPTURE=1 node --test test/tui-capture.test.mjs

Update and Uninstall

pi update --extensions                  # update installed pi packages
pi update npm:@senad-d/branchme        # update BranchMe only
pi remove npm:@senad-d/branchme        # remove global install
pi remove npm:@senad-d/branchme -l     # remove project-local install

Development

npm install
npm run typecheck
npm run format:check
npm run test
npm run check:pack
npm run validate
npm run smoke:pi:packed
pi --no-extensions -e .

npm run check:pack verifies the npm package does not include local state, specs, caches, node_modules, real environment files, or other private development artifacts. The safe .env.example template is included. npm run smoke:pi is the checkout runtime smoke and verifies BranchMe command output plus real Pi tool registration metadata. npm run smoke:pi:packed is the release-gate smoke: it packs the npm artifact into a temporary directory, installs it with production dependency settings, and runs pi against the installed package.


Publishing

BranchMe publishes to npm as @senad-d/branchme. You need an npm account with publish access to the @senad-d scope.

npm login
npm whoami
npm run release:check # optional preflight; every publish path runs this gate
node scripts/publish-npm.mjs

npm run release:check is the canonical release gate: it runs checkout validation and then installs and loads the packed npm artifact in isolation. Both the local publish script and the GitHub Publish to npm workflow run this gate before npm publication; a failure prevents publication and the workflow's Git tag creation.

The publish script requires a clean working tree, asks for the version number, runs npm run release:check, runs npm version <version> to update package.json and package-lock.json, creates the v<version> git tag, publishes with npm publish --access public, and then offers to push the release commit and tag.

Run it only from a clean working tree after updating CHANGELOG.md.

License

MIT