@senad-d/branchme 0.3.5 → 0.3.6

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 CHANGED
@@ -1,6 +1,11 @@
1
1
  # Changelog
2
2
 
3
- ## 0.3.5 - Unreleased
3
+ ## 0.3.6 - Unreleased
4
+
5
+ - Resolved Sonar maintainability findings by using a set for integration-branch membership and non-mutating sorting for changed worktree paths.
6
+
7
+ - `create_branch`, `change_branch`, `pull_branch` and `push_branch` receipts carry the resulting full HEAD (`head`); `push_branch` also names the remote-tracking ref it updated (`remoteTrackingRef`) and its commit after the push (`remoteHead`), and the text result says whether it matches the local HEAD. A caller no longer needs `branch_status` after these calls to learn or prove the HEAD.
8
+ ## 0.3.5 - Released
4
9
 
5
10
  - `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
11
  - 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senad-d/branchme",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
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
@@ -563,7 +563,7 @@ async function resolvePushTarget(
563
563
  };
564
564
  }
565
565
 
566
- const INTEGRATION_BRANCH_NAMES = ["main", "master", "trunk", "develop"];
566
+ const INTEGRATION_BRANCH_NAMES = new Set(["main", "master", "trunk", "develop"]);
567
567
 
568
568
  async function isIntegrationBranch(
569
569
  pi: Pick<ExtensionAPI, "exec">,
@@ -573,7 +573,7 @@ async function isIntegrationBranch(
573
573
  signal?: AbortSignal,
574
574
  ): Promise<boolean> {
575
575
  const branch = remoteRef.slice("refs/heads/".length);
576
- if (INTEGRATION_BRANCH_NAMES.includes(branch)) return true;
576
+ if (INTEGRATION_BRANCH_NAMES.has(branch)) return true;
577
577
  return remote === "origin" && branch === await getOriginDefaultBranch(pi, ctx, signal);
578
578
  }
579
579
 
@@ -1828,7 +1828,7 @@ function parseWorktreeRemovalStatus(output: string): WorktreeRemovalStatus {
1828
1828
  return {
1829
1829
  workingTree: parseWorkingTreeStatus(output).workingTree,
1830
1830
  ignoredPaths: [...ignoredPaths].sort(compareIgnoredPaths),
1831
- changedPaths: changedPaths.sort(compareIgnoredPaths),
1831
+ changedPaths: [...changedPaths].sort(compareIgnoredPaths),
1832
1832
  };
1833
1833
  }
1834
1834
 
@@ -2687,8 +2687,9 @@ export async function createLocalBranch(
2687
2687
  signal,
2688
2688
  timeout: GIT_MUTATION_TIMEOUT_MS,
2689
2689
  });
2690
+ const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
2690
2691
 
2691
- return { repoRoot, previousBranch: safeDetail(previousBranch), newBranch: safeDetail(branchName) };
2692
+ return { repoRoot, previousBranch: safeDetail(previousBranch), newBranch: safeDetail(branchName), head };
2692
2693
  });
2693
2694
  }
2694
2695
 
@@ -2726,6 +2727,7 @@ export async function changeExistingLocalBranch(
2726
2727
  if (current.detached || current.currentBranch !== branchName) {
2727
2728
  throw new Error(`git switch did not end on branch '${redactSecrets(branchName)}'.`);
2728
2729
  }
2730
+ const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
2729
2731
 
2730
2732
  return {
2731
2733
  repoRoot,
@@ -2733,6 +2735,7 @@ export async function changeExistingLocalBranch(
2733
2735
  previousDetached: previous.detached,
2734
2736
  currentBranch: safeDetail(current.currentBranch),
2735
2737
  hasChangesBeforeSwitch: false,
2738
+ head,
2736
2739
  };
2737
2740
  });
2738
2741
  }
@@ -2882,6 +2885,7 @@ export async function pullCurrentBranch(
2882
2885
  signal,
2883
2886
  timeout: GIT_PULL_TIMEOUT_MS,
2884
2887
  });
2888
+ const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
2885
2889
 
2886
2890
  return {
2887
2891
  repoRoot,
@@ -2890,6 +2894,7 @@ export async function pullCurrentBranch(
2890
2894
  remote: safeDetail(target.remote),
2891
2895
  remoteRef: safeDetail(target.remoteRef),
2892
2896
  output: safeOutput(result.stdout || result.stderr),
2897
+ head,
2893
2898
  };
2894
2899
  });
2895
2900
  }
@@ -2981,6 +2986,11 @@ export async function pushCurrentBranch(
2981
2986
  signal,
2982
2987
  timeout: GIT_PUSH_TIMEOUT_MS,
2983
2988
  });
2989
+ // The push updated the remote-tracking ref of the pushed branch; reading it
2990
+ // back is the remote-visible HEAD the receipt proves, without another fetch.
2991
+ const remoteTrackingRef = `${target.remote}/${target.remoteRef.replace(/^refs\/heads\//u, "")}`;
2992
+ const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
2993
+ const remoteHead = await getRemoteTrackingRefCommit(pi, rootCtx, remoteTrackingRef, signal);
2984
2994
 
2985
2995
  return {
2986
2996
  repoRoot,
@@ -2991,6 +3001,9 @@ export async function pushCurrentBranch(
2991
3001
  remoteRef: safeDetail(target.remoteRef),
2992
3002
  refspec: safeDetail(target.refspec),
2993
3003
  output: safeOutput(result.stdout || result.stderr),
3004
+ head,
3005
+ remoteTrackingRef: safeDetail(remoteTrackingRef),
3006
+ remoteHead,
2994
3007
  };
2995
3008
  });
2996
3009
  }
@@ -245,7 +245,7 @@ export function formatBranchStatus(details: GitContextDetails): string {
245
245
 
246
246
  export function formatChangeBranch(details: ChangeBranchDetails): string {
247
247
  const previous = details.previousDetached ? "detached HEAD" : details.previousBranch ?? "unknown branch";
248
- return `Changed branch from ${previous} to ${details.currentBranch}.`;
248
+ return `Changed branch from ${previous} to ${details.currentBranch} at ${details.head}.`;
249
249
  }
250
250
 
251
251
  export function formatPullRequest(details: PullRequestToolDetails, autofilledFields: PullRequestInputField[] = []): string {
@@ -641,7 +641,7 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
641
641
  content: [
642
642
  {
643
643
  type: "text",
644
- text: `Created and checked out branch ${details.newBranch} from ${details.previousBranch}.`,
644
+ text: `Created and checked out branch ${details.newBranch} from ${details.previousBranch} at ${details.head}.`,
645
645
  },
646
646
  ],
647
647
  details,
@@ -735,7 +735,7 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
735
735
  async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
736
736
  const details = await pullCurrentBranch(pi, ctx, signal);
737
737
  return {
738
- content: [{ type: "text", text: `Pulled current branch ${details.currentBranch} with fast-forward-only semantics.` }],
738
+ content: [{ type: "text", text: `Pulled current branch ${details.currentBranch} with fast-forward-only semantics; HEAD ${details.head}.` }],
739
739
  details,
740
740
  };
741
741
  },
@@ -846,8 +846,9 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
846
846
  async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
847
847
  const details = await pushCurrentBranch(pi, ctx, signal);
848
848
  const action = details.mode === "publish" ? "Published" : "Pushed";
849
+ const remoteState = details.remoteHead === details.head ? "matches" : `is at ${details.remoteHead}`;
849
850
  return {
850
- content: [{ type: "text", text: `${action} current branch ${details.currentBranch}.` }],
851
+ content: [{ type: "text", text: `${action} current branch ${details.currentBranch} at ${details.head}; ${details.remoteTrackingRef} ${remoteState}.` }],
851
852
  details,
852
853
  };
853
854
  },
package/src/types.ts CHANGED
@@ -280,6 +280,8 @@ export interface CreateBranchDetails {
280
280
  repoRoot: string;
281
281
  previousBranch: string;
282
282
  newBranch: string;
283
+ /** Full commit identity the new branch starts at (HEAD after the switch). */
284
+ head: string;
283
285
  }
284
286
 
285
287
  export interface ChangeBranchDetails {
@@ -288,6 +290,8 @@ export interface ChangeBranchDetails {
288
290
  previousDetached: boolean;
289
291
  currentBranch: string;
290
292
  hasChangesBeforeSwitch: false;
293
+ /** Full commit identity of HEAD after the switch. */
294
+ head: string;
291
295
  }
292
296
 
293
297
  export interface FetchBranchDetails {
@@ -318,6 +322,8 @@ export interface PullBranchDetails {
318
322
  remote: string;
319
323
  remoteRef: string;
320
324
  output: string;
325
+ /** Full commit identity of HEAD after the fast-forward. */
326
+ head: string;
321
327
  }
322
328
 
323
329
  export interface RebaseBranchDetails {
@@ -338,6 +344,12 @@ export interface PushBranchDetails {
338
344
  remoteRef: string;
339
345
  refspec: string;
340
346
  output: string;
347
+ /** Full commit identity of the local HEAD that was pushed. */
348
+ head: string;
349
+ /** Remote-tracking ref updated by the push, for example origin/feature/x. */
350
+ remoteTrackingRef: string;
351
+ /** Full commit identity of that remote-tracking ref after the push: the remote-visible HEAD. */
352
+ remoteHead: string;
341
353
  }
342
354
 
343
355
  export type IntegrateBranchStatus = "already_integrated" | "fast_forward" | "merge_commit" | "conflict" | "conflict_kept";