From cb1eb7745a7a74ad0143f32a67fa510686d0e4a4 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Fri, 10 Apr 2026 07:48:00 -0700 Subject: [PATCH] fix(core): preserve letter suffix case in normalizePhaseName (#1963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(core): preserve letter suffix case in normalizePhaseName (#1962) normalizePhaseName uppercased letter suffixes (e.g., "16c" → "16C"), causing directory/roadmap mismatches on case-sensitive filesystems. init progress couldn't match directory "16C-name" to roadmap "16c". Preserve original case — comparePhaseNum still uppercases for sorting (correct), but normalizePhaseName is used for display and directory creation where case must match the roadmap. Co-Authored-By: Claude Opus 4.6 (1M context) * test(phase): update existing test to expect preserved letter case The 'uppercases letters' test asserted the old behavior (3a → 03A). With normalizePhaseName now preserving case, update expectations to match (3a → 03a) and rename the test to 'preserves letter case'. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 (1M context) --- get-shit-done/bin/lib/core.cjs | 5 ++- tests/bug-1962-phase-suffix-case.test.cjs | 49 +++++++++++++++++++++++ tests/phase.test.cjs | 6 +-- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/bug-1962-phase-suffix-case.test.cjs diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 16957bbd..0e75314f 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -900,7 +900,10 @@ function normalizePhaseName(phase) { const match = stripped.match(/^(\d+)([A-Z])?((?:\.\d+)*)/i); if (match) { const padded = match[1].padStart(2, '0'); - const letter = match[2] ? match[2].toUpperCase() : ''; + // Preserve original case of letter suffix (#1962). + // Uppercasing causes directory/roadmap mismatches on case-sensitive filesystems + // (e.g., "16c" in ROADMAP.md → directory "16C-name" → progress can't match). + const letter = match[2] || ''; const decimal = match[3] || ''; return padded + letter + decimal; } diff --git a/tests/bug-1962-phase-suffix-case.test.cjs b/tests/bug-1962-phase-suffix-case.test.cjs new file mode 100644 index 00000000..1c00d0f0 --- /dev/null +++ b/tests/bug-1962-phase-suffix-case.test.cjs @@ -0,0 +1,49 @@ +/** + * Regression tests for bug #1962 + * + * normalizePhaseName must preserve the original case of letter suffixes. + * Uppercasing "16c" to "16C" causes directory/roadmap mismatches on + * case-sensitive filesystems — init progress can't match the directory + * back to the roadmap phase entry. + */ + +'use strict'; + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); + +const { normalizePhaseName } = require('../get-shit-done/bin/lib/core.cjs'); + +describe('bug #1962: normalizePhaseName preserves letter suffix case', () => { + test('lowercase suffix preserved: 16c → 16c', () => { + assert.equal(normalizePhaseName('16c'), '16c'); + }); + + test('uppercase suffix preserved: 16C → 16C', () => { + assert.equal(normalizePhaseName('16C'), '16C'); + }); + + test('single digit padded with lowercase suffix: 1a → 01a', () => { + assert.equal(normalizePhaseName('1a'), '01a'); + }); + + test('single digit padded with uppercase suffix: 1A → 01A', () => { + assert.equal(normalizePhaseName('1A'), '01A'); + }); + + test('no suffix unchanged: 16 → 16', () => { + assert.equal(normalizePhaseName('16'), '16'); + }); + + test('decimal suffix preserved: 16.1 → 16.1', () => { + assert.equal(normalizePhaseName('16.1'), '16.1'); + }); + + test('letter + decimal preserved: 16c.2 → 16c.2', () => { + assert.equal(normalizePhaseName('16c.2'), '16c.2'); + }); + + test('project code prefix stripped, suffix case preserved: CK-01a → 01a', () => { + assert.equal(normalizePhaseName('CK-01a'), '01a'); + }); +}); diff --git a/tests/phase.test.cjs b/tests/phase.test.cjs index 4225be9a..c06cf3d9 100644 --- a/tests/phase.test.cjs +++ b/tests/phase.test.cjs @@ -1906,9 +1906,9 @@ describe('normalizePhaseName', () => { assert.strictEqual(normalizePhaseName('12A.2'), '12A.2'); }); - test('uppercases letters', () => { - assert.strictEqual(normalizePhaseName('3a'), '03A'); - assert.strictEqual(normalizePhaseName('12b.1'), '12B.1'); + test('preserves letter case', () => { + assert.strictEqual(normalizePhaseName('3a'), '03a'); + assert.strictEqual(normalizePhaseName('12b.1'), '12b.1'); }); test('handles multi-level decimal phases', () => {