From 1657321eb03dbe1a8e2e0c37b7fa7e08fd2c5afc Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 20 Apr 2026 18:37:32 -0400 Subject: [PATCH] fix(install): remove bare ~/.claude reference in update.md (closes #2470) (#2482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(install): remove bare ~/.claude reference in update.md (closes #2470) The installer's copyWithPathReplacement() replaces ~/\.claude\/ (with trailing slash) but not ~/\.claude (bare, no trailing slash). A comment on line 398 of update.md used the bare form, which scanForLeakedPaths() correctly flagged for every non-Claude runtime install. Replaced the example in the comment with a non-Claude runtime path so the file passes the scanner for all runtimes. Co-Authored-By: Claude Sonnet 4.6 * fix(test): align regex with installer's word-boundary semantics (CodeRabbit #2482) Replace negative lookahead (?!\/) with \b word boundary to match the installer's scanForLeakedPaths() pattern. The lookahead would incorrectly flag ~/.claude_suffix whereas \b correctly excludes it. Co-Authored-By: Claude Sonnet 4.6 * fix(test): revert \b regex — (?!\/) was intentionally scoped to bare refs The installer's scanForLeakedPaths uses \b but the test is specifically checking for bare ~/.claude without trailing slash that the replacer misses. ~/.claude/ (with slash) at line 359 of update.md is expected and handled. \b would flag it as a false positive. Co-Authored-By: Claude Sonnet 4.6 * fix(inventory): update workflow count to 81 (graduation.md added in #2490) Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- docs/INVENTORY.md | 2 +- get-shit-done/workflows/update.md | 2 +- tests/bug-2470-update-md-claude-path.test.cjs | 36 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/bug-2470-update-md-claude-path.test.cjs diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 211c7d70..1a8bf039 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -173,7 +173,7 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md --- -## Workflows (80 shipped) +## Workflows (81 shipped) Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators that commands reference internally; most are not read directly by end users. Rows below map each workflow file to its role (derived from the `` block) and, where applicable, to the command that invokes it. diff --git a/get-shit-done/workflows/update.md b/get-shit-done/workflows/update.md index 94a873b2..d4712d3f 100644 --- a/get-shit-done/workflows/update.md +++ b/get-shit-done/workflows/update.md @@ -396,7 +396,7 @@ First, resolve the config directory (`RUNTIME_DIR`) from the install scope detected in `get_installed_version`: ```bash -# RUNTIME_DIR is the resolved config directory (e.g. ~/.claude, ~/.config/opencode) +# RUNTIME_DIR is the resolved config directory (e.g. ~/.config/opencode, ~/.gemini) # It should already be set from get_installed_version as GLOBAL_DIR or LOCAL_DIR. # Use the appropriate variable based on INSTALL_SCOPE. if [ "$INSTALL_SCOPE" = "LOCAL" ]; then diff --git a/tests/bug-2470-update-md-claude-path.test.cjs b/tests/bug-2470-update-md-claude-path.test.cjs new file mode 100644 index 00000000..0475f694 --- /dev/null +++ b/tests/bug-2470-update-md-claude-path.test.cjs @@ -0,0 +1,36 @@ +'use strict'; + +/** + * Regression test for #2470. + * + * update.md is installed into every runtime directory including .gemini, .codex, + * .opencode, etc. The installer's scanForLeakedPaths() uses the regex + * /(?:~|\$HOME)\/\.claude\b/g to detect unresolved .claude path references after + * copyWithPathReplacement() runs. The replacer handles "~/.claude/" (trailing slash) + * but not "~/.claude" (bare, no trailing slash) — so any bare reference in + * update.md would slip through and trigger the installer warning for non-Claude runtimes. + */ + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const UPDATE_MD = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'update.md'); + +describe('update.md — no bare ~.claude path references (#2470)', () => { + const content = fs.readFileSync(UPDATE_MD, 'utf-8'); + + test('update.md does not contain bare ~/\\.claude (without trailing slash)', () => { + // This is the exact pattern from the installer's scanForLeakedPaths(): + // /(?:~|\$HOME)\/\.claude\b/g + // The replacer handles ~/\.claude\/ (with trailing slash) but misses bare ~/\.claude + // so we must not have bare references in the source file. + const matches = content.match(/(?:~|\$HOME)\/\.claude(?!\/)/g); + assert.strictEqual( + matches, + null, + `update.md must not contain bare ~/\.claude (without trailing slash) — installer scanner flags these as unresolved path refs: ${JSON.stringify(matches)}` + ); + }); +});