From de7cedb053922879e80b074fa4409ff23ef08530 Mon Sep 17 00:00:00 2001 From: Suleiman Shahbari Date: Thu, 16 Jul 2026 17:59:57 +0300 Subject: [PATCH] fix(framework): on-before-mergeable run stays on the session branch (#560) The follow-up fired after setReadyForMerge() was spawned as a plain `framework prompt` child, so it inherited the #326 system prompt's `### Session name` step and committed + branched + checked out a fresh `the-framework/` branch before writing anything. Its output (the #556 quality TODO entries and the #537 knowledge docs) landed there, where nothing merges it, so the next run branched from main and could not see it. The follow-up is a follow-up to a session, not a session of its own. Spawn it `--vanilla` so it drops the built-in prompt (and its session-name step) and stays on the session's current branch, where its output rides to review and merge with the work. Scoped to the on-before-mergeable spawn; the maintainability/security preset spawns are untouched. Closes #560 --- .changeset/on-before-mergeable-no-branch.md | 5 +++++ packages/framework/src/cli.test.ts | 9 +++++++++ packages/framework/src/cli.ts | 16 ++++++++++++---- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .changeset/on-before-mergeable-no-branch.md diff --git a/.changeset/on-before-mergeable-no-branch.md b/.changeset/on-before-mergeable-no-branch.md new file mode 100644 index 0000000..4544fd7 --- /dev/null +++ b/.changeset/on-before-mergeable-no-branch.md @@ -0,0 +1,5 @@ +--- +"@gemstack/framework": patch +--- + +The on-before-mergeable follow-up no longer strands its output on a branch nothing merges. It was spawned as a plain `framework prompt` run, so it inherited the #326 system prompt's `### Session name` step and committed + created + checked out a fresh `the-framework/` branch before writing anything. Its output (the queued quality TODO entries and the business-knowledge docs, `DECISIONS.md` / `KNOWLEDGE-BASE.md`) landed on that branch, which nothing merges, so the next run branched from main and could not see it. The follow-up is a follow-up to a session, not a session of its own, so it now runs `--vanilla` (no built-in prompt, hence no session-name step) and stays on the session's current branch, where its output rides to review and merge with the work. diff --git a/packages/framework/src/cli.test.ts b/packages/framework/src/cli.test.ts index fe596b7..81f6fd0 100644 --- a/packages/framework/src/cli.test.ts +++ b/packages/framework/src/cli.test.ts @@ -100,6 +100,15 @@ test('promptRunArgs runs a headless prompt and carries NO --on-before-mergeable assert.equal(args.includes('--on-before-mergeable'), false) // maxCost is optional. assert.equal(promptRunArgs('x', '/w', '/bin/f').includes('--max-cost'), false) + // Not vanilla by default: a normal direct/quality run keeps the built-in #326 prompt. + assert.equal(args.includes('--vanilla'), false) +}) + +test('promptRunArgs adds --vanilla so the on-before-mergeable follow-up skips the session-name branch step (#560)', () => { + // The follow-up is not a session; --vanilla drops the #326 prompt (and its `### Session + // name` step), so the run stays on the session branch instead of stranding its output. + const args = promptRunArgs('queue follow-ups', '/work/app', '/bin/framework', 3, true) + assert.ok(args.includes('--vanilla')) }) test('runOnBeforeMergeable queues the follow-ups in ONE run instead of running the presets (#326/#556)', async () => { diff --git a/packages/framework/src/cli.ts b/packages/framework/src/cli.ts index 406f9b5..d3c2255 100644 --- a/packages/framework/src/cli.ts +++ b/packages/framework/src/cli.ts @@ -1388,17 +1388,24 @@ function spawnMaintenanceRun(review: RepoReview, binPath: string, maxCost?: numb * note it carries **no** `--on-before-mergeable`, which is the on-before-mergeable recursion guard (a quality * pass must not trigger its own suite). */ -export function promptRunArgs(prompt: string, cwd: string, binPath: string, maxCost?: number): string[] { +export function promptRunArgs(prompt: string, cwd: string, binPath: string, maxCost?: number, vanilla = false): string[] { const args = [binPath, 'prompt', prompt, '--no-dashboard', '--cwd', cwd] if (maxCost !== undefined) args.push('--max-cost', String(maxCost)) + // The on-before-mergeable follow-up runs `--vanilla` so it skips the #326 prompt's + // `### Session name` step: it is a follow-up to a session, not a session of its own, + // so it must not commit + branch + checkout a new `the-framework/` branch. That + // stranded its output (the #556 TODO entries and #537 knowledge docs) on a branch + // nothing merges (#560). Vanilla keeps it on the session's current branch, where its + // output rides to review and merge with the work. The follow-up prompt is self-contained. + if (vanilla) args.push('--vanilla') return args } -function spawnPromptRun(prompt: string, cwd: string, binPath: string, maxCost?: number): Promise { +function spawnPromptRun(prompt: string, cwd: string, binPath: string, maxCost?: number, vanilla = false): Promise { if (process.env.NODE_TEST_CONTEXT || /\.test\.[cm]?[jt]s$/.test(binPath)) { return Promise.resolve(false) // refuse to spawn from a test entry } - const args = promptRunArgs(prompt, cwd, binPath, maxCost) + const args = promptRunArgs(prompt, cwd, binPath, maxCost, vanilla) return new Promise(resolvePromise => { const child = spawn(process.execPath, args, { stdio: 'inherit' }) child.once('error', () => resolvePromise(false)) @@ -1426,7 +1433,8 @@ export async function runOnBeforeMergeable( tf: OnBeforeMergeableContext, maxCost?: number, eco?: EcoOptions, - run: PromptRunner = spawnPromptRun, + // Vanilla by default: the follow-up must not run the session-name step and branch (#560). + run: PromptRunner = (prompt, cwd, binPath, maxCost) => spawnPromptRun(prompt, cwd, binPath, maxCost, true), fs: StoreFs = nodeStoreFs(), ): Promise { // Ensure the presets exist so the queued entries' filePaths resolve, even in a repo