@senad-d/branchme 0.3.12 → 0.3.13
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 +4 -0
- package/README.md +6 -5
- package/SECURITY.md +2 -2
- package/docs/PROJECT_DEFINITION_BRIEF.md +3 -3
- package/docs/SMOKE_TEST.md +1 -1
- package/docs/STRUCTURE.md +2 -2
- package/docs/TUI_CAPTURE.md +1 -1
- package/package.json +1 -1
- package/src/commands/branchme-command.ts +1 -1
- package/src/git-integration.ts +2 -0
- package/src/tools/branchme-tools.ts +5 -3
- package/src/tools/workflow-tools.ts +2 -2
- package/src/types.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.13 - Unreleased
|
|
4
|
+
|
|
5
|
+
- Added optional `integrate_branch.keepConflicts` (default `false`). When true, a conflicted local integration returns `conflict_kept` with the merge left in progress for `conclude_merge` to finish after resolution or abort; default conflict handling still aborts and verifies restoration. Updated schemas, prompt guidance, documentation, and real-Git regression coverage.
|
|
6
|
+
|
|
3
7
|
## 0.3.12 - Unreleased
|
|
4
8
|
|
|
5
9
|
- Added `pull_request_files` (read-only: a PR's first 100 changed files with change type and line counts, plus its labels) and `label_pull_request` (adds 1 to 5 existing repository labels to a PR; GitHub would create an unknown label, so it is refused first). An agent can now choose and set a PR's area label from what it changes.
|
package/README.md
CHANGED
|
@@ -273,14 +273,14 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
273
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
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
275
|
| `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. |
|
|
276
|
-
| `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
|
|
276
|
+
| `integrate_branch` | `{ "sourceBranch": string, "targetBranch": string, "keepConflicts"?: boolean }` | 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. |
|
|
277
277
|
| `create_branch` | `{ "branchName": string }` | Validates `branchName`, rejects existing local branches, and runs `git switch -c <branchName>` from current `HEAD`. |
|
|
278
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
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`. |
|
|
280
280
|
|
|
281
281
|
`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
282
|
|
|
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` 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.
|
|
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` 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` 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
284
|
|
|
285
285
|
---
|
|
286
286
|
|
|
@@ -340,9 +340,10 @@ The structured status is one of:
|
|
|
340
340
|
- `already_integrated`: no merge ran and both branch refs remained stable;
|
|
341
341
|
- `fast_forward`: the target advanced exactly to the captured source commit;
|
|
342
342
|
- `merge_commit`: Git created an exact normal two-parent merge whose parents are the captured prior target and source;
|
|
343
|
-
- `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
|
|
343
|
+
- `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;
|
|
344
|
+
- `conflict_kept` (only with `keepConflicts: true`): the conflict paths were captured and the merge was left in progress with MERGE_HEAD at the captured source commit, HEAD unchanged, and no other operation active; `conclude_merge` commits it after the markers are removed or aborts it.
|
|
344
345
|
|
|
345
|
-
|
|
346
|
+
Successful and automatically aborted outcomes include captured before/after source and target commit IDs plus final source and prior-target ancestry proof. `conflict_kept` instead includes the captured source and target commit IDs and in-progress merge facts. 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.
|
|
346
347
|
|
|
347
348
|
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.
|
|
348
349
|
|
|
@@ -538,7 +539,7 @@ Ensure the token and Git credentials have permission for the branch and pull req
|
|
|
538
539
|
| 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. |
|
|
539
540
|
| 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. |
|
|
540
541
|
| 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. |
|
|
541
|
-
| `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. |
|
|
542
|
+
| `integrate_branch` or `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. |
|
|
542
543
|
| `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. |
|
|
543
544
|
| 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. |
|
|
544
545
|
| 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. |
|
package/SECURITY.md
CHANGED
|
@@ -25,7 +25,7 @@ Implemented git mutations are limited to:
|
|
|
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
27
|
- `rebase_branch`: `git rebase --no-autostash --no-update-refs <upstream>` for the clean current branch after validating its configured upstream target. It rewrites local commits and automatically attempts `git rebase --abort` without the cancelled caller signal if the rebase fails or is killed.
|
|
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.
|
|
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.
|
|
31
31
|
- `remove_worktree`: `git worktree remove <verifiedCanonicalPath>` without force, only after fresh repository-membership, safety-state, path, tracked/untracked status, and ignored-entry checks. The local branch is retained and verified at the same commit.
|
|
@@ -81,7 +81,7 @@ The branch preflight requests have no body. BranchMe uses the resolved `headBran
|
|
|
81
81
|
- `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
82
|
- `pull_branch` accepts no parameters, updates only the clean current branch from its configured upstream, and uses fast-forward-only semantics.
|
|
83
83
|
- `rebase_branch` accepts no parameters, rebases only the clean current branch onto its configured upstream, disables autostash and multi-ref updates, never pushes, and attempts to abort on failure.
|
|
84
|
-
- `integrate_branch`
|
|
84
|
+
- `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
85
|
- 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
86
|
- If local `origin` and `GITHUB_REPOSITORY` both resolve but disagree, PR creation and related-PR lookup fail closed.
|
|
87
87
|
- Resolved PR branches are validated as distinct, existing local branch-name refs; identical or missing local branches and cross-repository `head` values are rejected before any GitHub request.
|
|
@@ -62,7 +62,7 @@ Originally approved on 2026-06-30. Updated to describe the implemented `0.3.1` p
|
|
|
62
62
|
| Tool | `reply_pull_request_feedback` | Answer PR feedback | One reply in a review thread of the PR (optionally resolving it) or one conversation comment; the thread must belong to the PR; never edits, deletes, approves, merges or closes |
|
|
63
63
|
| Tool | `init_repository` | Initialize the exact current directory as a new non-bare Git repository | Optional initial branch; rejects reinitialization/nesting; no path, commit, or project-file controls |
|
|
64
64
|
| Tool | `branch_status` | Refresh bounded current-worktree Git and related-PR context; optionally prove captured local or remote-tracking ancestry | Read-only; targeted ancestry is absent from automatic context |
|
|
65
|
-
| Tool | `integrate_branch` | Integrate one exact local source into the already-current clean local target | Fixed normal-merge policy; verified automatic abort/restoration on conflict |
|
|
65
|
+
| Tool | `integrate_branch` | Integrate one exact local source into the already-current clean local target | Fixed normal-merge policy; verified automatic abort/restoration on conflict unless `keepConflicts` |
|
|
66
66
|
| Tool | `list_worktrees` | List bounded main/linked worktree inventory | Read-only and explicit |
|
|
67
67
|
| Tool | `create_worktree` | Create and verify a linked worktree | Exact absolute handoff cwd; new/existing local branch modes only |
|
|
68
68
|
| Tool | `remove_worktree` | Remove an exact verified clean linked worktree | Force-free; ignored entries protected by default and deletable with explicit `deleteIgnored: true`; branch retained |
|
|
@@ -141,7 +141,7 @@ Standalone removal retains its branch and requires explicit `deleteIgnored: true
|
|
|
141
141
|
|
|
142
142
|
## 8. Branch integration contract
|
|
143
143
|
|
|
144
|
-
- `integrate_branch` accepts exactly `sourceBranch` and `targetBranch`. They must identify distinct existing local refs in the current repository; remote-only refs, full refs, commit IDs, paths, repositories, remotes, and owner-prefixed refs are rejected.
|
|
144
|
+
- `integrate_branch` accepts exactly `sourceBranch` and `targetBranch`, plus optional boolean `keepConflicts`. They must identify distinct existing local refs in the current repository; remote-only refs, full refs, commit IDs, paths, repositories, remotes, and owner-prefixed refs are rejected.
|
|
145
145
|
- The active Pi worktree is the control worktree and must already have the target checked out, be clean, and have no merge/rebase/cherry-pick/revert/sequencer state. The source may be checked out in another dirty worktree because only its captured commit ref is read.
|
|
146
146
|
- Before mutation, BranchMe rejects a non-empty `branch.<targetBranch>.mergeOptions` setting so branch-specific defaults cannot change the policy. The fixed command is `git -c rerere.enabled=false merge --ff --no-edit --no-autostash --no-rerere-autoupdate --no-overwrite-ignore refs/heads/<sourceBranch>`. It does not fetch or push, disables autostash and rerere, protects ignored files, and preserves hooks, custom merge drivers, clean/smudge filters, and signing policy.
|
|
147
147
|
- Results distinguish `already_integrated`, `fast_forward`, `merge_commit`, and `conflict`. Before/after source and target commit IDs, repository/control-worktree identity, clean/operation state, and source/prior-target ancestry are verified. Merge-commit classification requires exact captured target/source parents.
|
|
@@ -200,7 +200,7 @@ Standalone removal retains its branch and requires explicit `deleteIgnored: true
|
|
|
200
200
|
- Slash commands remain informational; tools perform all Git and GitHub actions.
|
|
201
201
|
- Automatic context remains focused on the active worktree; inventory is available only through `list_worktrees`.
|
|
202
202
|
- `create_worktree` returns a verified target for a caller-managed session but does not change cwd or create processes.
|
|
203
|
-
- `integrate_branch` and `update_from_base` use the verified normal-merge state machine and automatically abort initial conflicts; only `update_from_base` fetches an explicit remote base before merging its captured commit, and
|
|
203
|
+
- `integrate_branch` and `update_from_base` use the verified normal-merge state machine and automatically abort initial conflicts; only `update_from_base` fetches an explicit remote base before merging its captured commit, and either may keep a conflicted merge in progress with `keepConflicts: true`. `conclude_merge` is the single continuation surface: it commits only marker-free formerly unmerged paths with Git's prepared message or aborts with verified restoration, and performs no semantic resolution.
|
|
204
204
|
- Worktree removal remains force-free, protects ignored entries by default, requires explicit authorization to delete them, and preserves the local branch.
|
|
205
205
|
- Local branch retirement remains a separate explicit operation requiring a fresh expected `HEAD`, exact target ancestry, zero complete-inventory occupancy, and an explicit boolean force decision; it deletes only the leased local ref and leaves branch configuration and remote/remote-tracking refs untouched.
|
|
206
206
|
- `push_branch` uses `origin` only when the current branch has no configured upstream.
|
package/docs/SMOKE_TEST.md
CHANGED
|
@@ -24,7 +24,7 @@ pi --no-extensions -e .
|
|
|
24
24
|
|
|
25
25
|
- `npm run smoke:pi` first runs isolated checkout Pi processes from a temporary non-Git working directory: one with `pi --no-extensions -e <package> -e <temporary verifier>` and `/branchmeverify verify`, then one with `pi --no-extensions -e <package>` and `/branchme help`.
|
|
26
26
|
- The temporary command verifier calls `pi.getAllTools()` after BranchMe loads and confirms exactly twenty-five tools—`fetch_remote`, `list_branches`, `track_branch`, `update_from_base`, `conclude_merge`, `pull_request_status`, `pull_request_feedback`, `reply_pull_request_feedback`, `pull_request_files`, `label_pull_request`, `branch_status`, `init_repository`, `change_branch`, `create_branch`, `create_worktree`, `fetch_branch`, `integrate_branch`, `land_branch`, `list_worktrees`, `pull_branch`, `pull_request`, `push_branch`, `rebase_branch`, `remove_worktree`, and `retire_branch`—are each registered exactly once and active with strict schemas, named prompt guidelines, descriptions, and extension source metadata. It also proves `continue_merge` and `abort_merge` are absent.
|
|
27
|
-
- Runtime schema inspection verifies that `integrate_branch` has exactly the required `sourceBranch` and `targetBranch` fields and that `retire_branch` has exactly the required `branchName`, `expectedHead`, `targetBranch`, and `force` fields with `additionalProperties: false`. It also verifies that `branch_status` has only an optional top-level `ancestry` object whose required nested `sourceBranch` and `targetBranch` fields reject additional nested or top-level properties.
|
|
27
|
+
- Runtime schema inspection verifies that `integrate_branch` has exactly the required `sourceBranch` and `targetBranch` fields plus optional `keepConflicts` and that `retire_branch` has exactly the required `branchName`, `expectedHead`, `targetBranch`, and `force` fields with `additionalProperties: false`. It also verifies that `branch_status` has only an optional top-level `ancestry` object whose required nested `sourceBranch` and `targetBranch` fields reject additional nested or top-level properties.
|
|
28
28
|
- A second temporary verifier registers a deterministic local smoke model, blocks `fetch`, and runs normal prompts through real Pi lifecycle handling. It verifies automatic no-tool Git context in a temporary repository, one real `branch_status` tool refresh after a verifier-created local change, safe credential-free related-PR status with no request, and non-Git startup fallback. It expects all twenty-five tools but forbids `land_branch`, `retire_branch`, `integrate_branch`, `conclude_merge`, and every remote or worktree mutation tool (including `fetch_remote`) from executing.
|
|
29
29
|
- The Pi runtime smoke validates worktree, integration, and retirement tool registration, strict schemas, and prompt contracts only; it never creates or removes a worktree, runs a merge, or retires a branch. Real-Git lifecycle coverage runs under `npm run test` in isolated temporary local repositories with no remote contact.
|
|
30
30
|
- Isolated real-Git integration tests cover `already_integrated`, `fast_forward`, exact two-parent `merge_commit`, and conflict-path capture followed by verified automatic abort/restoration. They also cover target mismatch, dirty control state, rejection of branch-specific target merge options, unrelated histories, ignored-file overwrite protection, preserved repository hooks, and a committed source ref checked out in another dirty linked worktree. The recorded merge argv proves autostash and rerere are disabled, ignored-file protection is enabled, and `--no-verify` is absent.
|
package/docs/STRUCTURE.md
CHANGED
|
@@ -53,7 +53,7 @@ src/
|
|
|
53
53
|
- No long-lived processes, watchers, timers, sockets, or background jobs start in the extension factory.
|
|
54
54
|
- A single `before_agent_start` handler synchronously collects a fresh snapshot for each agent run and appends it to the existing system prompt; failures degrade to bounded unavailable context rather than blocking startup.
|
|
55
55
|
- Slash commands are informational; tools perform repository initialization, branch, integration, retirement, worktree, push, and PR actions. Commands never initialize repositories, create/remove worktrees, merge or retire branches, change cwd, or start Pi sessions. There is no context command.
|
|
56
|
-
- Every tool uses a strict TypeBox object schema with `additionalProperties: false`; `init_repository` accepts only optional `initialBranch`; `integrate_branch` requires exactly `sourceBranch` and `targetBranch`; `retire_branch` requires exactly `branchName`, full commit `expectedHead`, distinct `targetBranch`, and boolean `force`; `update_from_base` accepts optional boolean `keepConflicts`; `conclude_merge` requires exactly `action` (`conclude` or `abort`); worktree creation requires `worktreePath`, `branchName`, and `branchMode` with optional new-mode `baseRef`, listing is empty, and removal requires `worktreePath` with optional boolean `deleteIgnored`.
|
|
56
|
+
- Every tool uses a strict TypeBox object schema with `additionalProperties: false`; `init_repository` accepts only optional `initialBranch`; `integrate_branch` requires exactly `sourceBranch` and `targetBranch` with optional boolean `keepConflicts`; `retire_branch` requires exactly `branchName`, full commit `expectedHead`, distinct `targetBranch`, and boolean `force`; `update_from_base` accepts optional boolean `keepConflicts`; `conclude_merge` requires exactly `action` (`conclude` or `abort`); worktree creation requires `worktreePath`, `branchName`, and `branchMode` with optional new-mode `baseRef`, listing is empty, and removal requires `worktreePath` with optional boolean `deleteIgnored`.
|
|
57
57
|
- Every tool defines a description, `promptSnippet`, and tool-specific `promptGuidelines` that explicitly name the tool.
|
|
58
58
|
- Git commands use argv arrays with cwd, signal, and timeout; network calls use the transport wrapper's absolute executable and child environment, while other commands use `pi.exec("git", args, { cwd, signal, timeout })`; `init_repository` uses Pi's file-mutation queue for the new `.git` entry and queues on the canonical target directory, and repository mutations run from verified locations and same-repository mutation/PR windows are serialized per repository. Retirement uses one canonical-active-worktree-keyed queue window from preflight through final verification. The queue is process-local and does not lock a different active worktree, another Pi process, or an external Git process.
|
|
59
59
|
- Worktree results expose serializable requested and verified before/after state. Create returns `handoff: { cwd: <absolute>, branch, head, ready: true, summary }`; remove returns the retained branch/HEAD with `cwd: null` and `ready: false`.
|
|
@@ -74,7 +74,7 @@ src/
|
|
|
74
74
|
- `pull_branch` requires a clean worktree and configured upstream, then updates only the current branch with an explicit `git pull --ff-only --no-rebase --no-autostash <remote> <remote-ref>` command; divergence fails without a rebase or merge commit.
|
|
75
75
|
- `rebase_branch` requires a clean worktree and configured upstream, then rebases only the current branch with `git rebase --no-autostash --no-update-refs <upstream>`; it rewrites local commits and automatically attempts `git rebase --abort` on failure.
|
|
76
76
|
- `integrate_branch` requires distinct existing local refs and a clean control worktree already on the target. It rejects non-empty target-branch `mergeOptions`, runs `git -c rerere.enabled=false merge --ff --no-edit --no-autostash --no-rerere-autoupdate --no-overwrite-ignore refs/heads/<sourceBranch>`, verifies before/after refs and ancestry, and classifies already-integrated, fast-forward, exact two-parent merge-commit, or conflict.
|
|
77
|
-
- Conflict status is emitted only after bounded lossless paths are captured, `git merge --abort` succeeds, and repository identity, exact refs, target checkout, absent operation state, and clean worktree restoration are verified. With `update_from_base` `keepConflicts: true`, `conflict_kept` is emitted only after the paths are captured and MERGE_HEAD, unchanged HEAD, branch, repository identity, and single-merge operation state are verified; a failed capture falls back to the verified abort. Failed or uncertain postconditions throw; no reset rollback exists.
|
|
77
|
+
- Conflict status is emitted only after bounded lossless paths are captured, `git merge --abort` succeeds, and repository identity, exact refs, target checkout, absent operation state, and clean worktree restoration are verified. With `integrate_branch` or `update_from_base` `keepConflicts: true`, `conflict_kept` is emitted only after the paths are captured and MERGE_HEAD, unchanged HEAD, branch, repository identity, and single-merge operation state are verified; a failed capture falls back to the verified abort. Failed or uncertain postconditions throw; no reset rollback exists.
|
|
78
78
|
- `conclude_merge` operates only when MERGE_HEAD contains exactly one commit on a current direct local branch. `conclude` checks unmerged working-tree paths and staged changed blobs for default/custom-sized conflict markers (including diff3, CRLF, and binary content), stages exactly the currently unmerged paths with `git --literal-pathspecs add --`, rechecks the candidate index to cover clean filters, commits the full index with `git commit --no-edit`, and verifies MERGE_HEAD is gone, HEAD moved on the same branch, and HEAD has exactly the previous HEAD and MERGE_HEAD as parents; unrelated unstaged changes stay unstaged. Already-staged entries are included in the commit; callers must not stage unrelated edits during a merge. Pre-mutation reads honor cancellation; after staging begins, bounded mutation/verification ignores cancellation. Lost commit responses report uncertain postconditions. `abort` reuses the integration abort and restoration verification.
|
|
79
79
|
- `retire_branch` requires one direct local ref, its exact full expected `HEAD`, one distinct direct local target, and a boolean force decision. It rejects complete-inventory worktree occupancy, proves ancestry against captured commits, and allows negative ancestry only with explicit `force: true` authorization.
|
|
80
80
|
- Retirement deletes only `refs/heads/<branchName>` with `git update-ref --no-deref -d <fullRef> <capturedHead>`. The expected-old-value lease prevents deleting a moved ref; final repository, target, retiring-ref, and occupancy checks run with bounded timeouts after the mutation attempt even if the caller cancels. Contradictory or inconclusive state throws an uncertain error with manual-inspection guidance and no rollback ref mutation.
|
package/docs/TUI_CAPTURE.md
CHANGED
|
@@ -52,7 +52,7 @@ Commands only show info; BranchMe tools perform actions.
|
|
|
52
52
|
## Branch integration
|
|
53
53
|
|
|
54
54
|
- `integrate_branch` — integrate an exact local source into an exact local target; the clean control worktree must already have the target checked out.
|
|
55
|
-
- It never fetches or pushes; a conflict is automatically aborted after paths are captured and restoration is verified
|
|
55
|
+
- It never fetches or pushes; a conflict is automatically aborted after paths are captured and restoration is verified, unless `keepConflicts: true` leaves it in progress for `conclude_merge`.
|
|
56
56
|
- BranchMe does not resolve semantic conflicts; handle them in a separate developer workflow.
|
|
57
57
|
|
|
58
58
|
## Branch retirement
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senad-d/branchme",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi extension for verified Git repository initialization, branch, worktree, integration, retirement, push, and pull request workflows.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,7 +61,7 @@ export function getBranchMeHelpText(): string {
|
|
|
61
61
|
"## Branch integration",
|
|
62
62
|
"",
|
|
63
63
|
"- `integrate_branch` — integrate an exact local source into an exact local target; the clean control worktree must already have the target checked out.",
|
|
64
|
-
"- It never fetches or pushes; a conflict is automatically aborted after paths are captured and restoration is verified
|
|
64
|
+
"- It never fetches or pushes; a conflict is automatically aborted after paths are captured and restoration is verified, unless `keepConflicts: true` leaves it in progress for `conclude_merge`.",
|
|
65
65
|
"- BranchMe does not resolve semantic conflicts; handle them in a separate developer workflow.",
|
|
66
66
|
"",
|
|
67
67
|
"## Branch retirement",
|
package/src/git-integration.ts
CHANGED
|
@@ -133,6 +133,7 @@ const IntegrateBranchParametersSchema = Type.Object(
|
|
|
133
133
|
{
|
|
134
134
|
sourceBranch: Type.String({ minLength: 1, description: "Exact existing local source branch to integrate." }),
|
|
135
135
|
targetBranch: Type.String({ minLength: 1, description: "Exact existing local target branch already checked out in the clean control worktree." }),
|
|
136
|
+
keepConflicts: Type.Optional(Type.Boolean({ description: "When true, a conflicted merge is left in progress (MERGE_HEAD set, conflicted paths unmerged) for conclude_merge instead of being aborted; defaults to false." })),
|
|
136
137
|
},
|
|
137
138
|
{ additionalProperties: false },
|
|
138
139
|
);
|
|
@@ -772,15 +773,16 @@ export function registerBranchMeTools(host: Pick<ExtensionAPI, "registerTool" |
|
|
|
772
773
|
pi.registerTool({
|
|
773
774
|
name: INTEGRATE_BRANCH_TOOL_NAME,
|
|
774
775
|
label: "Integrate Branch",
|
|
775
|
-
description: "integrate_branch verifies and merges one exact existing local source branch into one exact existing local target branch from the current clean control worktree, returning an already-integrated, fast-forward, merge-commit, or automatically aborted
|
|
776
|
-
promptSnippet: "integrate_branch: merge an exact local source branch into the checked-out clean local target with verified outcomes and automatic conflict abort",
|
|
776
|
+
description: "integrate_branch verifies and merges one exact existing local source branch into one exact existing local target branch from the current clean control worktree, returning an already-integrated, fast-forward, merge-commit, or conflict result. A conflict is automatically aborted with restoration verified unless keepConflicts is true, which leaves the conflicted merge in progress (conflict_kept) for conclude_merge. integrate_branch never fetches or pushes.",
|
|
777
|
+
promptSnippet: "integrate_branch: merge an exact local source branch into the checked-out clean local target with verified outcomes and automatic conflict abort; keepConflicts keeps a conflicted merge in progress",
|
|
777
778
|
promptGuidelines: [
|
|
778
779
|
"Use integrate_branch only when the user explicitly wants one exact existing local source branch integrated into one exact existing local target branch.",
|
|
779
780
|
"Before integrate_branch, require the current clean control worktree to already have targetBranch checked out; integrate_branch never switches branches, stashes, or discards changes.",
|
|
780
781
|
"Use integrate_branch only after local refs already contain the commits to integrate; integrate_branch never fetches or pushes.",
|
|
781
782
|
"Call integrate_branch by itself and wait for it to complete; never batch integrate_branch with other Git mutations.",
|
|
782
783
|
"After integrate_branch completes, run any targeted branch_status ancestry proof separately; never place integrate_branch and branch_status in the same parallel tool batch.",
|
|
783
|
-
"A conflict result from integrate_branch means the merge was automatically aborted and restoration was verified; semantic conflict analysis belongs to a separate delegated workflow, not integrate_branch.",
|
|
784
|
+
"A conflict result from integrate_branch means the merge was automatically aborted and restoration was verified unless keepConflicts is true; semantic conflict analysis belongs to a separate delegated workflow, not integrate_branch.",
|
|
785
|
+
"Pass keepConflicts: true to integrate_branch only when a file-editing step will resolve the listed paths; the result conflict_kept leaves the merge in progress with MERGE_HEAD set, and conclude_merge must then conclude or abort it before any other Git mutation.",
|
|
784
786
|
],
|
|
785
787
|
parameters: IntegrateBranchParametersSchema,
|
|
786
788
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
@@ -215,10 +215,10 @@ export function registerWorkflowTools(pi: Pick<ExtensionAPI, "registerTool" | "e
|
|
|
215
215
|
pi.registerTool({
|
|
216
216
|
name: CONCLUDE_MERGE_TOOL_NAME,
|
|
217
217
|
label: "Conclude Merge",
|
|
218
|
-
description: "conclude_merge finishes or abandons the in-progress merge on the current checkout, normally one kept by update_from_base with keepConflicts: true. action \"conclude\" checks unmerged working-tree paths and staged blobs for default/custom-sized conflict marker lines, stages exactly the unmerged paths, checks the candidate index again, and commits the full index with Git's prepared merge message, then verifies the two-parent result; action \"abort\" runs git merge --abort and verifies restoration. conclude_merge never stages other changes, accepts no commit message, and never pushes.",
|
|
218
|
+
description: "conclude_merge finishes or abandons the in-progress merge on the current checkout, normally one kept by update_from_base or integrate_branch with keepConflicts: true. action \"conclude\" checks unmerged working-tree paths and staged blobs for default/custom-sized conflict marker lines, stages exactly the unmerged paths, checks the candidate index again, and commits the full index with Git's prepared merge message, then verifies the two-parent result; action \"abort\" runs git merge --abort and verifies restoration. conclude_merge never stages other changes, accepts no commit message, and never pushes.",
|
|
219
219
|
promptSnippet: "conclude_merge: commit or abort the kept in-progress merge after conflict markers are removed",
|
|
220
220
|
promptGuidelines: [
|
|
221
|
-
"Use conclude_merge only after update_from_base reported conflict_kept, or when a single-head merge is otherwise in progress on the current checkout; run conclude_merge by itself. Multi-head merges are refused before mutation.",
|
|
221
|
+
"Use conclude_merge only after update_from_base or integrate_branch reported conflict_kept, or when a single-head merge is otherwise in progress on the current checkout; run conclude_merge by itself. Multi-head merges are refused before mutation.",
|
|
222
222
|
"Before conclude_merge with action conclude, a file-editing step must remove every <<<<<<<, |||||||, =======, and >>>>>>> marker line from the listed paths; conclude_merge refuses and names the paths that still contain markers.",
|
|
223
223
|
"conclude_merge with action conclude stages only the currently unmerged paths and uses Git's prepared merge message; unrelated unstaged edits stay unstaged and are not part of the merge commit. Git commits the entire index, including previously staged entries: do not stage unrelated edits during the merge.",
|
|
224
224
|
"conclude_merge with action abort restores the branch with git merge --abort and verifies a clean idle checkout; conclude_merge refuses when no merge is in progress.",
|
package/src/types.ts
CHANGED
|
@@ -86,6 +86,8 @@ export interface InitRepositoryDetails {
|
|
|
86
86
|
export interface IntegrateBranchToolInput {
|
|
87
87
|
sourceBranch: string;
|
|
88
88
|
targetBranch: string;
|
|
89
|
+
/** Leave a conflicted merge in progress for conclude_merge instead of aborting it. */
|
|
90
|
+
keepConflicts?: boolean;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
export interface RetireBranchToolInput {
|
|
@@ -440,7 +442,7 @@ export interface IntegrateBranchConflictDetails extends IntegrateBranchDetailsBa
|
|
|
440
442
|
};
|
|
441
443
|
}
|
|
442
444
|
|
|
443
|
-
/** A conflicted merge deliberately left in progress (update_from_base keepConflicts); conclude_merge finishes or abandons it. */
|
|
445
|
+
/** A conflicted merge deliberately left in progress (integrate_branch or update_from_base keepConflicts); conclude_merge finishes or abandons it. */
|
|
444
446
|
export interface IntegrateBranchConflictKeptDetails {
|
|
445
447
|
action: "integrate_branch";
|
|
446
448
|
status: "conflict_kept";
|