From afb5d9a479e9c766c378cc520d3f4b99bc9acc16 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sat, 25 Jul 2026 20:07:58 -0700 Subject: [PATCH] Guard non-standard default branches like conventional ones Cursor Bugbot: sitting on a non-standard default such as trunk (the origin/HEAD target) still ran candidate selection, where develop or main could win and produce an integration-vs-integration diff. The guard now treats the remote default branch as an integration branch alongside the conventional names. Failing-first test: on trunk with a develop branch present, the scope stays the working tree. Prepared with AI assistance (Claude Code), directed by @pbakaus. Co-Authored-By: Claude Code --- skill/scripts/context-signals.mjs | 8 +++++--- tests/context-signals.test.mjs | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/skill/scripts/context-signals.mjs b/skill/scripts/context-signals.mjs index 61763e1bb..15e78213d 100644 --- a/skill/scripts/context-signals.mjs +++ b/skill/scripts/context-signals.mjs @@ -112,13 +112,15 @@ function gitSignals(cwd) { // signal may override that: an origin/HEAD or upstream naming a DIFFERENT // integration branch (sitting on develop while the remote default is // main) would produce exactly the integration-vs-integration divergence - // this detection exists to prevent. - const onIntegrationBranch = conventional.includes(branch); + // this detection exists to prevent. "Integration branch" means a + // conventional name OR the remote's default branch, so a non-standard + // default like trunk is guarded the same way. + const remoteHead = splitRemoteRef(run(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'])); + const onIntegrationBranch = conventional.includes(branch) || branch === remoteHead?.name; let base = null; let baseRev = null; if (!onIntegrationBranch) { const upstream = asUpstream(run(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'])); - const remoteHead = splitRemoteRef(run(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'])); const candidates = []; const seen = new Set(); const addCandidate = (name, revs) => { diff --git a/tests/context-signals.test.mjs b/tests/context-signals.test.mjs index 432737282..e2172d5da 100644 --- a/tests/context-signals.test.mjs +++ b/tests/context-signals.test.mjs @@ -376,6 +376,26 @@ describe('gatherSignals', () => { assert.deepEqual(s.git.changedFiles, ['src/Hero.tsx']); }); + it('sitting on a non-standard default branch keeps the working-tree scope (#302)', async () => { + const { execFileSync } = await import('node:child_process'); + const git = (...args) => execFileSync('git', args, { cwd: scratch, stdio: 'ignore' }); + git('init', '-q', '-b', 'trunk'); + git('config', 'user.email', 't@example.com'); + git('config', 'user.name', 'Test'); + write('src/App.tsx', 'export default 1;\n'); + git('add', '.'); + git('commit', '-qm', 'init'); + git('branch', '-q', 'develop'); // a conventional name also exists + git('update-ref', 'refs/remotes/origin/trunk', 'HEAD'); + git('symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/trunk'); + write('src/App.tsx', 'export default 2;\n'); // dirty on trunk + const s = await gatherSignals(scratch); + // trunk IS the integration branch (origin/HEAD says so); develop must + // not win the candidate scan and produce a trunk-vs-develop diff. + assert.equal(s.git.base, null); + assert.deepEqual(s.git.changedFiles, ['src/App.tsx']); + }); + it('never diffs one integration branch against another (#302)', async () => { const { execFileSync } = await import('node:child_process'); const git = (...args) => execFileSync('git', args, { cwd: scratch, stdio: 'ignore' });