From afba2afbe7e044edce5ce968e275b83c514fbe1d Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 26 May 2026 12:06:57 -0400 Subject: [PATCH] fix(#229): normalize gsd-hook-version substitution in verify-reapply (#282) --- .changeset/jolly-hawks-forage.md | 5 +++ get-shit-done/bin/verify-reapply-patches.cjs | 20 ++++++++++-- .../bug-2969-verify-reapply-patches.test.cjs | 31 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 .changeset/jolly-hawks-forage.md diff --git a/.changeset/jolly-hawks-forage.md b/.changeset/jolly-hawks-forage.md new file mode 100644 index 000000000..c535945d5 --- /dev/null +++ b/.changeset/jolly-hawks-forage.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 229 +--- +Fixed verify-reapply false fail_user_lines_missing when gsd-hook-version lines are install-time substituted. diff --git a/get-shit-done/bin/verify-reapply-patches.cjs b/get-shit-done/bin/verify-reapply-patches.cjs index 76bef6429..2bd44d696 100755 --- a/get-shit-done/bin/verify-reapply-patches.cjs +++ b/get-shit-done/bin/verify-reapply-patches.cjs @@ -34,6 +34,7 @@ const path = require('node:path'); const crypto = require('node:crypto'); const SIGNIFICANT_MIN_CHARS = 12; +const GSD_HOOK_VERSION_LINE_RE = /^(?:\/\/|#)\s*gsd-hook-version:\s*\S+\s*$/i; function parseArgs(argv) { const opts = { patchesDir: null, configDir: null, pristineDir: null, json: false }; @@ -67,6 +68,16 @@ function isSignificantLine(line) { return true; } +function normalizeUpstreamOwnedLine(line) { + const trimmed = line.trim(); + if (!trimmed) return line; + if (GSD_HOOK_VERSION_LINE_RE.test(trimmed)) { + const prefix = trimmed.startsWith('#') ? '#' : '//'; + return `${prefix} gsd-hook-version: __GSD_VERSION_TOKEN__`; + } + return line; +} + /** * Compute the SHA-256 hex digest of a string (UTF-8 encoded). */ @@ -124,8 +135,13 @@ function computeUserAddedLines(backupContent, pristineContent) { if (!pristineContent) { return backupLines.filter(isSignificantLine); } - const pristineSet = new Set(pristineContent.split(/\r?\n/)); - return backupLines.filter((line) => isSignificantLine(line) && !pristineSet.has(line)); + const pristineSet = new Set( + pristineContent.split(/\r?\n/).map(normalizeUpstreamOwnedLine), + ); + return backupLines.filter((line) => { + if (!isSignificantLine(line)) return false; + return !pristineSet.has(normalizeUpstreamOwnedLine(line)); + }); } /** diff --git a/tests/bug-2969-verify-reapply-patches.test.cjs b/tests/bug-2969-verify-reapply-patches.test.cjs index 4c44c8dda..90a8fed89 100644 --- a/tests/bug-2969-verify-reapply-patches.test.cjs +++ b/tests/bug-2969-verify-reapply-patches.test.cjs @@ -210,4 +210,35 @@ describe('Bug #2969: deterministic Step 5 verification gate', () => { assert.ok(report.results[0].missing.includes(droppedLine)); assert.ok(!report.results[0].missing.includes(presentLine)); }); + + test('treats gsd-hook-version install-time substitution as upstream-owned, not missing user content (#229)', () => { + resetFixture(); + const rel = path.join('hooks', 'gsd-statusline.js'); + const pristine = [ + '// gsd-hook-version: {{GSD_VERSION}}', + 'console.log("statusline hook");', + '', + ].join('\n'); + const backup = [ + '// gsd-hook-version: 1.41.0', + 'console.log("statusline hook");', + '', + ].join('\n'); + const installed = [ + '// gsd-hook-version: 1.42.3', + 'console.log("statusline hook");', + '', + ].join('\n'); + + writeFile(path.join(pristineDir, rel), pristine); + writeFile(path.join(patchesDir, rel), backup); + writeFile(path.join(configDir, rel), installed); + + const { status, report } = runVerifier(); + assert.equal(status, 0, `expected pass for upstream-owned version substitution; report=${JSON.stringify(report)}`); + assert.equal(report.failures, 0); + assert.equal(report.checked, 1); + assert.equal(report.results[0].status, 'ok'); + assert.deepStrictEqual(report.results[0].missing, []); + }); });