From cb2cda1865e8c4f9a2136c1a33a928dab93f3846 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 8 Jun 2026 22:51:38 -0400 Subject: [PATCH] fix(#904): normalize phase number in init.execute-phase branch_name (#909) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the {phase} substitution in normalizePhaseName() at both fix sites: - src/init.cts — cmdInitExecutePhase branch_name output - src/commands.cts — cmdCommit pre-execution branch derivation When project_code is set (e.g. "CK"), extractPhaseToken returns the full prefixed token "CK-01" as phase_number. Without normalization the generated branch was "gsd/phase-CK-01-foundation"; after this fix it is "gsd/phase-01-foundation", matching the documented {phase} contract (padded numeric only). Adds a regression test in tests/init.test.cjs. Co-authored-by: Claude Opus 4.8 --- ...904-init-execute-phase-normalize-branch.md | 5 ++++ src/commands.cts | 2 +- src/init.cts | 2 +- tests/init.test.cjs | 29 +++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 .changeset/904-init-execute-phase-normalize-branch.md diff --git a/.changeset/904-init-execute-phase-normalize-branch.md b/.changeset/904-init-execute-phase-normalize-branch.md new file mode 100644 index 000000000..3e1a3dd6c --- /dev/null +++ b/.changeset/904-init-execute-phase-normalize-branch.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 904 +--- +**`init execute-phase` and `cmdCommit` now produce correct `branch_name` when `project_code` is set** — the `{phase}` substitution in `phase_branch_template` now calls `normalizePhaseName()`, stripping the project-code prefix and zero-padding the number, so the generated branch is e.g. `gsd/phase-01-foundation` instead of `gsd/phase-CK-01-foundation`. Both the execute-phase output path (`src/init.cts`) and the pre-execution commit path (`src/commands.cts`) are fixed. (#904) diff --git a/src/commands.cts b/src/commands.cts index 0d72e81bc..78e37f5f4 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -547,7 +547,7 @@ function cmdCommit(cwd: string, message: string | undefined, files: string[] | u const phaseInfo = findPhaseInternal(cwd, phaseNum) as Record | null; if (phaseInfo) { branchName = (config['phase_branch_template'] as string) - .replace('{phase}', phaseInfo['phase_number'] as string) + .replace('{phase}', normalizePhaseName(phaseInfo['phase_number'])) .replace('{slug}', (phaseInfo['phase_slug'] as string) || 'phase'); } } diff --git a/src/init.cts b/src/init.cts index 444ae31d3..a6379a169 100644 --- a/src/init.cts +++ b/src/init.cts @@ -260,7 +260,7 @@ function cmdInitExecutePhase( config.branching_strategy === 'phase' && phaseInfo ? (config.phase_branch_template as string) .replace('{project}', (config.project_code as string) || '') - .replace('{phase}', phaseInfo['phase_number'] as string) + .replace('{phase}', normalizePhaseName(phaseInfo['phase_number'])) .replace('{slug}', (phaseInfo['phase_slug'] as string) || 'phase') : config.branching_strategy === 'milestone' ? (config.milestone_branch_template as string) diff --git a/tests/init.test.cjs b/tests/init.test.cjs index ec5957998..14bbe38ec 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -538,6 +538,35 @@ describe('init plan-phase zero-padded phase number (bug #2391)', () => { assert.strictEqual(out03.phase_req_ids, out3.phase_req_ids, 'phase_req_ids must be identical regardless of padding'); }); + + // ── #904: branch_name must use normalized (stripped + zero-padded) phase number ── + // When project_code is set (e.g. "CK") the phase directory is prefixed: + // "CK-01-foundation". extractPhaseToken returns "CK-01" as phase_number. + // branch_name must call normalizePhaseName so it strips the prefix and zero-pads, + // producing "gsd/phase-01-foundation" rather than "gsd/phase-CK-01-foundation". + test('branch_name uses normalized phase number when project_code prefixes phase dir (#904)', () => { + seedPhase(tmpDir, 'CK-01-foundation', { + 'CK-01-01-PLAN.md': '# Plan', + }); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ + project_code: 'CK', + git: { + branching_strategy: 'phase', + phase_branch_template: 'gsd/phase-{phase}-{slug}', + }, + }, null, 2) + ); + + const result = runGsdTools('init execute-phase 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + // branch_name must use the normalized phase number, not the raw "CK-01" token + assert.strictEqual(output.branch_name, 'gsd/phase-01-foundation', + 'branch_name must use normalized phase number (strip project_code prefix, zero-pad), not raw phase_number'); + }); }); // ─────────────────────────────────────────────────────────────────────────────