@senad-d/branchme 0.3.4 → 0.3.5
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 +6 -1
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/git.ts +72 -7
- package/src/tools/branchme-tools.ts +14 -3
- package/src/types.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.3.
|
|
3
|
+
## 0.3.5 - Unreleased
|
|
4
|
+
|
|
5
|
+
- `push_branch` never pushes onto a differently named integration branch: when the current branch's upstream is `main`, `master`, `trunk`, `develop`, or the origin default branch under another name (a feature branch created from `origin/main` with tracking), it publishes to the same-named branch on that remote with `--set-upstream`, reports `mode: "publish"`, and keeps the previous upstream in `upstream`. Previously such a branch was pushed straight onto the integration branch. Other differently named upstreams keep their explicit `HEAD:<upstream-ref>` push.
|
|
6
|
+
- Added optional `remove_worktree.discardChanges` (default `false`, unchanged behavior). When `true`, the user's explicit authorization, a worktree with staged, unstaged, untracked, or unmerged changes is removed with `git worktree remove --force` and the result lists every discarded path in `discardedPaths`; without it a dirty worktree is still refused, now naming the option. Locked, detached, main, current, prunable, missing, and foreign worktrees stay rejected, and the local branch is retained.
|
|
7
|
+
|
|
8
|
+
## 0.3.4 - Released
|
|
4
9
|
|
|
5
10
|
- Added `fetch_remote` for tool-native discovery of unknown remote branch names: refresh one configured remote's branch cache (default `origin`) with optional, remote-tracking-only pruning (default false). Atomic explicit mappings disable configured refmaps, tags, tag pruning, submodules, and maintenance; symbolic destination checks include dangling loose refs, and unsupported non-files ref backends fail closed.
|
|
6
11
|
- Extended read-only `list_branches` with optional local/remote-tracking `kind` and bounded Git branch-list glob `patterns`, applied before output limits. Documented the sequential replacement for issue branch discovery via `git fetch origin --prune` and `git branch -r --list`.
|
package/README.md
CHANGED
|
@@ -262,7 +262,7 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
262
262
|
| `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. |
|
|
263
263
|
| `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. |
|
|
264
264
|
| `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. |
|
|
265
|
-
| `remove_worktree` | `{ "worktreePath": string, "deleteIgnored"?: boolean }` | Force-free removal of an explicitly selected, verified clean linked worktree. Ignored residue is refused by default; `deleteIgnored: true` explicitly deletes it and reports its top-level paths. The local branch remains at the same commit. |
|
|
265
|
+
| `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. |
|
|
266
266
|
| `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. |
|
|
267
267
|
| `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. |
|
|
268
268
|
| `change_branch` | `{ "branchName": string }` | Validates `branchName`, requires `refs/heads/<branchName>` to exist locally, rejects dirty worktrees, and runs `git switch <branchName>`. |
|
|
@@ -271,7 +271,7 @@ Commands are informational only. BranchMe actions are performed by agent-callabl
|
|
|
271
271
|
| `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. |
|
|
272
272
|
| `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. |
|
|
273
273
|
| `create_branch` | `{ "branchName": string }` | Validates `branchName`, rejects existing local branches, and runs `git switch -c <branchName>` from current `HEAD`. |
|
|
274
|
-
| `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. |
|
|
274
|
+
| `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. |
|
|
275
275
|
| `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`. |
|
|
276
276
|
|
|
277
277
|
`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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senad-d/branchme",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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",
|
package/src/git.ts
CHANGED
|
@@ -534,6 +534,26 @@ async function resolvePushTarget(
|
|
|
534
534
|
};
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
const sameNameRef = `refs/heads/${currentBranch}`;
|
|
538
|
+
if (
|
|
539
|
+
upstreamTarget.remoteRef !== sameNameRef &&
|
|
540
|
+
await isIntegrationBranch(pi, ctx, upstreamTarget.remote, upstreamTarget.remoteRef, signal)
|
|
541
|
+
) {
|
|
542
|
+
// A feature branch tracking the default or an integration branch (created
|
|
543
|
+
// from origin/main with tracking) would push straight onto it. Publish to
|
|
544
|
+
// the same-named branch instead and track that; the result reports the
|
|
545
|
+
// previous upstream.
|
|
546
|
+
const refspec = `HEAD:${sameNameRef}`;
|
|
547
|
+
return {
|
|
548
|
+
upstream: upstreamTarget.upstream,
|
|
549
|
+
mode: "publish",
|
|
550
|
+
remote: upstreamTarget.remote,
|
|
551
|
+
remoteRef: sameNameRef,
|
|
552
|
+
refspec,
|
|
553
|
+
args: ["push", "--set-upstream", upstreamTarget.remote, refspec],
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
537
557
|
const refspec = `HEAD:${upstreamTarget.remoteRef}`;
|
|
538
558
|
return {
|
|
539
559
|
...upstreamTarget,
|
|
@@ -543,6 +563,20 @@ async function resolvePushTarget(
|
|
|
543
563
|
};
|
|
544
564
|
}
|
|
545
565
|
|
|
566
|
+
const INTEGRATION_BRANCH_NAMES = ["main", "master", "trunk", "develop"];
|
|
567
|
+
|
|
568
|
+
async function isIntegrationBranch(
|
|
569
|
+
pi: Pick<ExtensionAPI, "exec">,
|
|
570
|
+
ctx: GitCommandContext,
|
|
571
|
+
remote: string,
|
|
572
|
+
remoteRef: string,
|
|
573
|
+
signal?: AbortSignal,
|
|
574
|
+
): Promise<boolean> {
|
|
575
|
+
const branch = remoteRef.slice("refs/heads/".length);
|
|
576
|
+
if (INTEGRATION_BRANCH_NAMES.includes(branch)) return true;
|
|
577
|
+
return remote === "origin" && branch === await getOriginDefaultBranch(pi, ctx, signal);
|
|
578
|
+
}
|
|
579
|
+
|
|
546
580
|
export async function hasWorkingTreeChanges(
|
|
547
581
|
pi: Pick<ExtensionAPI, "exec">,
|
|
548
582
|
ctx: GitCommandContext,
|
|
@@ -1762,6 +1796,7 @@ async function requirePresentWorktreeDirectory(canonicalPath: string): Promise<v
|
|
|
1762
1796
|
interface WorktreeRemovalStatus {
|
|
1763
1797
|
workingTree: WorkingTreeDetails;
|
|
1764
1798
|
ignoredPaths: string[];
|
|
1799
|
+
changedPaths: string[];
|
|
1765
1800
|
}
|
|
1766
1801
|
|
|
1767
1802
|
function compareIgnoredPaths(left: string, right: string): number {
|
|
@@ -1775,6 +1810,7 @@ function parseWorktreeRemovalStatus(output: string): WorktreeRemovalStatus {
|
|
|
1775
1810
|
|
|
1776
1811
|
const records = output.split(NUL_SEPARATOR);
|
|
1777
1812
|
const ignoredPaths = new Set<string>();
|
|
1813
|
+
const changedPaths: string[] = [];
|
|
1778
1814
|
let skipNextRecord = false;
|
|
1779
1815
|
for (const [index, record] of records.entries()) {
|
|
1780
1816
|
if (skipNextRecord) {
|
|
@@ -1786,11 +1822,13 @@ function parseWorktreeRemovalStatus(output: string): WorktreeRemovalStatus {
|
|
|
1786
1822
|
const parsed = parsePorcelainChange(records, index);
|
|
1787
1823
|
skipNextRecord = parsed.nextIndex > index;
|
|
1788
1824
|
if (parsed.status === "!!") ignoredPaths.add(parsed.path.split("/")[0]);
|
|
1825
|
+
else changedPaths.push(parsed.path);
|
|
1789
1826
|
}
|
|
1790
1827
|
|
|
1791
1828
|
return {
|
|
1792
1829
|
workingTree: parseWorkingTreeStatus(output).workingTree,
|
|
1793
1830
|
ignoredPaths: [...ignoredPaths].sort(compareIgnoredPaths),
|
|
1831
|
+
changedPaths: changedPaths.sort(compareIgnoredPaths),
|
|
1794
1832
|
};
|
|
1795
1833
|
}
|
|
1796
1834
|
|
|
@@ -1799,10 +1837,13 @@ async function inspectRemovableWorktree(
|
|
|
1799
1837
|
canonicalPath: string,
|
|
1800
1838
|
signal?: AbortSignal,
|
|
1801
1839
|
allowIgnored = false,
|
|
1840
|
+
allowDirty = false,
|
|
1802
1841
|
): Promise<WorktreeRemovalStatus> {
|
|
1803
1842
|
const workingTree = await inspectWorktreeState(pi, canonicalPath, signal);
|
|
1804
|
-
if (workingTree.state !== "clean") {
|
|
1805
|
-
throw new Error(
|
|
1843
|
+
if (workingTree.state !== "clean" && !allowDirty) {
|
|
1844
|
+
throw new Error(
|
|
1845
|
+
"The target worktree has staged, unstaged, untracked, or unmerged changes; clean it before removal, or pass discardChanges: true when the user explicitly authorized discarding them.",
|
|
1846
|
+
);
|
|
1806
1847
|
}
|
|
1807
1848
|
|
|
1808
1849
|
const ignoredResult = await runGit(pi, { cwd: canonicalPath }, GIT_WORKTREE_REMOVAL_IGNORED_STATUS_ARGS, {
|
|
@@ -1810,7 +1851,7 @@ async function inspectRemovableWorktree(
|
|
|
1810
1851
|
timeout: GIT_STATUS_TIMEOUT_MS,
|
|
1811
1852
|
});
|
|
1812
1853
|
const removalStatus = parseWorktreeRemovalStatus(ignoredResult.stdout);
|
|
1813
|
-
if (removalStatus.workingTree.state !== "clean") {
|
|
1854
|
+
if (removalStatus.workingTree.state !== "clean" && !allowDirty) {
|
|
1814
1855
|
throw new Error("The target worktree has staged, unstaged, untracked, or unmerged changes; clean it before removal.");
|
|
1815
1856
|
}
|
|
1816
1857
|
if (!allowIgnored && removalStatus.ignoredPaths.length > 0) {
|
|
@@ -1850,6 +1891,7 @@ export async function removeWorktree(
|
|
|
1850
1891
|
worktreePath: unknown,
|
|
1851
1892
|
signal?: AbortSignal,
|
|
1852
1893
|
deleteIgnored = false,
|
|
1894
|
+
discardChanges = false,
|
|
1853
1895
|
): Promise<RemoveWorktreeDetails> {
|
|
1854
1896
|
const requestedWorktreePath = validateWorktreePathInput(worktreePath);
|
|
1855
1897
|
const repoRoot = await getGitRoot(pi, ctx, signal);
|
|
@@ -1857,11 +1899,21 @@ export async function removeWorktree(
|
|
|
1857
1899
|
|
|
1858
1900
|
return withRepositoryMutationQueue(
|
|
1859
1901
|
repoRoot,
|
|
1860
|
-
removeWorktreeWithinQueue.bind(
|
|
1902
|
+
removeWorktreeWithinQueue.bind(
|
|
1903
|
+
undefined,
|
|
1904
|
+
pi,
|
|
1905
|
+
rootCtx,
|
|
1906
|
+
requestedWorktreePath,
|
|
1907
|
+
signal,
|
|
1908
|
+
deleteIgnored,
|
|
1909
|
+
undefined,
|
|
1910
|
+
discardChanges,
|
|
1911
|
+
),
|
|
1861
1912
|
);
|
|
1862
1913
|
}
|
|
1863
1914
|
|
|
1864
|
-
// Caller must hold the repository mutation queue. Ignored residue
|
|
1915
|
+
// Caller must hold the repository mutation queue. Ignored residue and discarded
|
|
1916
|
+
// changes each require explicit authorization.
|
|
1865
1917
|
export async function removeWorktreeWithinQueue(
|
|
1866
1918
|
pi: Pick<ExtensionAPI, "exec">,
|
|
1867
1919
|
rootCtx: GitCommandContext,
|
|
@@ -1869,6 +1921,7 @@ export async function removeWorktreeWithinQueue(
|
|
|
1869
1921
|
signal?: AbortSignal,
|
|
1870
1922
|
allowIgnored = false,
|
|
1871
1923
|
expectedIdentity?: { branch: string; head: string },
|
|
1924
|
+
allowDirty = false,
|
|
1872
1925
|
): Promise<RemoveWorktreeDetails & { deletedIgnoredPaths: string[] }> {
|
|
1873
1926
|
const repoRoot = rootCtx.cwd;
|
|
1874
1927
|
const resolved = await resolveWorktreeRemovalTarget(
|
|
@@ -1893,13 +1946,23 @@ export async function removeWorktreeWithinQueue(
|
|
|
1893
1946
|
const verifiedCanonicalPath = requireLosslessWorktreeIdentity(resolved.canonicalPath, "cwd");
|
|
1894
1947
|
const retainedBranch = requireLosslessWorktreeIdentity(branchName, "branch");
|
|
1895
1948
|
await requirePresentWorktreeDirectory(verifiedCanonicalPath);
|
|
1896
|
-
const { workingTree, ignoredPaths } = await inspectRemovableWorktree(
|
|
1949
|
+
const { workingTree, ignoredPaths, changedPaths } = await inspectRemovableWorktree(
|
|
1950
|
+
pi,
|
|
1951
|
+
verifiedCanonicalPath,
|
|
1952
|
+
signal,
|
|
1953
|
+
allowIgnored,
|
|
1954
|
+
allowDirty,
|
|
1955
|
+
);
|
|
1897
1956
|
const branchHeadBefore = await getLocalBranchCommit(pi, rootCtx, retainedBranch, signal);
|
|
1898
1957
|
if (branchHeadBefore.toLowerCase() !== head.toLowerCase()) {
|
|
1899
1958
|
throw new Error("The target local branch did not match the worktree HEAD before removal.");
|
|
1900
1959
|
}
|
|
1901
1960
|
|
|
1902
|
-
|
|
1961
|
+
// --force only when the user authorized discarding changes; a locked worktree
|
|
1962
|
+
// is rejected before this and would need a second --force anyway.
|
|
1963
|
+
const args = changedPaths.length > 0
|
|
1964
|
+
? ["worktree", "remove", "--force", verifiedCanonicalPath]
|
|
1965
|
+
: ["worktree", "remove", verifiedCanonicalPath];
|
|
1903
1966
|
try {
|
|
1904
1967
|
await runGit(pi, rootCtx, args, {
|
|
1905
1968
|
signal,
|
|
@@ -1927,10 +1990,12 @@ export async function removeWorktreeWithinQueue(
|
|
|
1927
1990
|
return {
|
|
1928
1991
|
action: "remove_worktree",
|
|
1929
1992
|
deletedIgnoredPaths: ignoredPaths.map((path) => redactSecrets(path)),
|
|
1993
|
+
discardedPaths: changedPaths.map((path) => redactSecrets(path)),
|
|
1930
1994
|
repoRoot: safeWorktreeValue(repoRoot, GIT_WORKTREE_PATH_LIMIT_CHARS),
|
|
1931
1995
|
request: {
|
|
1932
1996
|
worktreePath: safeWorktreeValue(requestedWorktreePath, GIT_WORKTREE_PATH_LIMIT_CHARS),
|
|
1933
1997
|
...(allowIgnored ? { deleteIgnored: true } : {}),
|
|
1998
|
+
...(allowDirty ? { discardChanges: true } : {}),
|
|
1934
1999
|
},
|
|
1935
2000
|
verified: {
|
|
1936
2001
|
before: {
|
|
@@ -209,6 +209,9 @@ const RemoveWorktreeParametersSchema = Type.Object(
|
|
|
209
209
|
deleteIgnored: Type.Optional(Type.Boolean({
|
|
210
210
|
description: "Explicit authorization to delete ignored files and directories with the worktree; defaults to false.",
|
|
211
211
|
})),
|
|
212
|
+
discardChanges: Type.Optional(Type.Boolean({
|
|
213
|
+
description: "Explicit authorization to discard staged, unstaged, untracked and unmerged changes with the worktree; defaults to false. The result lists every discarded path.",
|
|
214
|
+
})),
|
|
212
215
|
},
|
|
213
216
|
{ additionalProperties: false },
|
|
214
217
|
);
|
|
@@ -832,7 +835,7 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
|
|
|
832
835
|
pi.registerTool({
|
|
833
836
|
name: PUSH_BRANCH_TOOL_NAME,
|
|
834
837
|
label: "Push Branch",
|
|
835
|
-
description: "push_branch pushes the current branch to its configured upstream remote with an explicit refspec. If the current branch has no upstream, push_branch publishes it to origin with --set-upstream. push_branch never commits, stages, or edits files.",
|
|
838
|
+
description: "push_branch pushes the current branch to its configured upstream remote with an explicit refspec. If the current branch has no upstream, push_branch publishes it to origin with --set-upstream. A branch whose upstream is a differently named main, master, trunk, develop or origin default branch is never pushed onto it: push_branch publishes it to its own name on that remote, tracks it, and reports the previous upstream. push_branch never commits, stages, or edits files.",
|
|
836
839
|
promptSnippet: "push_branch: push or publish the current branch only with an explicit target, without committing or staging",
|
|
837
840
|
promptGuidelines: [
|
|
838
841
|
"Use push_branch only after commits already exist; push_branch never commits, stages, or edits files.",
|
|
@@ -957,17 +960,25 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
|
|
|
957
960
|
pi.registerTool({
|
|
958
961
|
name: REMOVE_WORKTREE_TOOL_NAME,
|
|
959
962
|
label: "Remove Worktree",
|
|
960
|
-
description: "remove_worktree force-free removes one verified clean linked worktree at an explicit absolute worktreePath while retaining and returning its exact local branch identity. Ignored residue is refused by default; deleteIgnored: true explicitly authorizes deleting it with the worktree and reports its top-level paths. remove_worktree rejects main, current,
|
|
963
|
+
description: "remove_worktree force-free removes one verified clean linked worktree at an explicit absolute worktreePath while retaining and returning its exact local branch identity. Ignored residue is refused by default; deleteIgnored: true explicitly authorizes deleting it with the worktree and reports its top-level paths. discardChanges: true explicitly authorizes discarding uncommitted changes and reports every discarded path. remove_worktree rejects main, current, detached, locked, prunable, missing, and foreign worktrees, rejects dirty ones without discardChanges, and never deletes branches.",
|
|
961
964
|
promptSnippet: "remove_worktree: force-free removal of an explicitly selected clean linked worktree, with optional explicit ignored-residue deletion, while retaining its branch",
|
|
962
965
|
promptGuidelines: [
|
|
963
966
|
"Use remove_worktree only when the user explicitly requests worktree removal and provides or approves the exact absolute worktreePath; remove_worktree must never infer a filesystem path silently.",
|
|
964
967
|
"Use remove_worktree with worktreePath and optional deleteIgnored; set deleteIgnored: true only after the user explicitly authorizes deleting all ignored files and directories, including possible .env, .pi/, dependency, and build residue.",
|
|
968
|
+
"Set remove_worktree discardChanges: true only after the user explicitly authorizes discarding the worktree's uncommitted changes; never infer it from a dirty worktree.",
|
|
965
969
|
"remove_worktree never accepts force, move, prune, repair, lock, unlock, branch deletion, remote, or refspec parameters.",
|
|
966
970
|
"Do not batch remove_worktree with dependent worktree mutations; wait for remove_worktree to complete and verify its non-ready handoff before continuing.",
|
|
967
971
|
],
|
|
968
972
|
parameters: RemoveWorktreeParametersSchema,
|
|
969
973
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
970
|
-
const details = await removeWorktree(
|
|
974
|
+
const details = await removeWorktree(
|
|
975
|
+
pi,
|
|
976
|
+
ctx,
|
|
977
|
+
params.worktreePath,
|
|
978
|
+
signal,
|
|
979
|
+
params.deleteIgnored === true,
|
|
980
|
+
params.discardChanges === true,
|
|
981
|
+
);
|
|
971
982
|
return {
|
|
972
983
|
content: [{ type: "text", text: formatRemoveWorktree(details) }],
|
|
973
984
|
details,
|
package/src/types.ts
CHANGED
|
@@ -111,6 +111,8 @@ export interface RemoveWorktreeToolInput {
|
|
|
111
111
|
worktreePath: string;
|
|
112
112
|
/** Explicitly authorizes deleting ignored files and directories with the worktree. */
|
|
113
113
|
deleteIgnored?: boolean;
|
|
114
|
+
/** Explicitly authorizes discarding staged, unstaged, untracked and unmerged changes with the worktree. */
|
|
115
|
+
discardChanges?: boolean;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
export interface WorktreeEntry {
|
|
@@ -194,6 +196,8 @@ export interface CreateWorktreeDetails {
|
|
|
194
196
|
export interface RemoveWorktreeDetails {
|
|
195
197
|
action: "remove_worktree";
|
|
196
198
|
deletedIgnoredPaths: string[];
|
|
199
|
+
/** Changed paths discarded under discardChanges; empty for a clean worktree. */
|
|
200
|
+
discardedPaths: string[];
|
|
197
201
|
repoRoot: string;
|
|
198
202
|
request: RemoveWorktreeToolInput;
|
|
199
203
|
verified: {
|