From e82653965c4f738990daa11b6784b9c52f2e75d7 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sat, 25 Jul 2026 20:00:10 -0700 Subject: [PATCH] An existing develop outranks a main-pointing origin/HEAD Cursor Bugbot's remaining round-1 finding held for the current code too: in a git-flow repo whose platform default was never flipped off main, a feature branch without an upstream picked origin/HEAD's main over the develop branch features actually merge to, dragging the develop-vs-main divergence into scan targets. develop now sits between the upstream signal and origin/HEAD in the candidate order; repos without a develop branch are unaffected. Failing-first test covers the exact shape (develop exists, origin/HEAD -> main). Prepared with AI assistance (Claude Code), directed by @pbakaus. Co-Authored-By: Claude Code --- skill/scripts/context-signals.mjs | 7 ++++++- tests/context-signals.test.mjs | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/skill/scripts/context-signals.mjs b/skill/scripts/context-signals.mjs index d37610343..61763e1bb 100644 --- a/skill/scripts/context-signals.mjs +++ b/skill/scripts/context-signals.mjs @@ -129,8 +129,13 @@ function gitSignals(cwd) { // The upstream tracks the actual merge target, so its own rev wins over // a possibly stale local branch of the same name. if (upstream) addCandidate(upstream.name, [upstream.rev]); + // A develop branch marks a git-flow repo where features merge to develop + // even when the platform default (origin/HEAD) was never flipped off + // main; an existing develop therefore outranks the remote default. This + // is #302's own repro shape, and repos without develop are unaffected. + addCandidate('develop', ['develop', 'origin/develop']); if (remoteHead) addCandidate(remoteHead.name, [remoteHead.name, remoteHead.rev]); - for (const name of conventional) addCandidate(name, [name, `origin/${name}`]); + for (const name of ['main', 'master']) addCandidate(name, [name, `origin/${name}`]); for (const c of candidates) { const rev = c.revs.find((r) => run(['rev-parse', '--verify', '--quiet', r]) !== null); if (rev) { diff --git a/tests/context-signals.test.mjs b/tests/context-signals.test.mjs index 3631f8d48..432737282 100644 --- a/tests/context-signals.test.mjs +++ b/tests/context-signals.test.mjs @@ -309,6 +309,31 @@ describe('gatherSignals', () => { assert.deepEqual(s.git.changedFiles, ['src/Hero.tsx']); }); + it('an existing develop outranks a main-pointing origin/HEAD (#302)', async () => { + const { execFileSync } = await import('node:child_process'); + const git = (...args) => execFileSync('git', args, { cwd: scratch, stdio: 'ignore' }); + git('init', '-q', '-b', 'main'); + 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'); + // Classic git-flow with the platform default never flipped off main. + git('update-ref', 'refs/remotes/origin/main', 'main'); + git('symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/main'); + git('checkout', '-q', 'develop'); + git('checkout', '-q', '-b', 'feature/g'); + write('src/Hero.tsx', 'export const Hero = () => null;\n'); + git('add', '.'); + git('commit', '-qm', 'feature work'); + const s = await gatherSignals(scratch); + // Features merge to develop here; picking origin/HEAD's main would drag + // the develop-vs-main divergence into scan targets. + assert.equal(s.git.base, 'develop'); + assert.deepEqual(s.git.changedFiles, ['src/Hero.tsx']); + }); + it('remote signals cannot bypass the integration-branch guard (#302)', async () => { const { execFileSync } = await import('node:child_process'); const git = (...args) => execFileSync('git', args, { cwd: scratch, stdio: 'ignore' });