diff --git a/bin/install.js b/bin/install.js index fa9aa3556..80c3969eb 100755 --- a/bin/install.js +++ b/bin/install.js @@ -46,6 +46,27 @@ function getDirName(runtime) { return '.claude'; } +/** + * Get the config directory path relative to home directory for a runtime + * Used for templating hooks that use path.join(homeDir, '', ...) + * @param {string} runtime - 'claude', 'opencode', or 'gemini' + * @param {boolean} isGlobal - Whether this is a global install + */ +function getConfigDirFromHome(runtime, isGlobal) { + if (!isGlobal) { + // Local installs use the same dir name pattern + return getDirName(runtime); + } + // Global installs - OpenCode uses XDG path structure + if (runtime === 'opencode') { + // OpenCode: ~/.config/opencode -> '.config', 'opencode' + // Return as comma-separated for path.join() replacement + return "'.config', 'opencode'"; + } + if (runtime === 'gemini') return "'.gemini'"; + return "'.claude'"; +} + /** * Get the global config directory for OpenCode * OpenCode follows XDG Base Directory spec and uses ~/.config/opencode/ @@ -626,9 +647,11 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) { const destPath = path.join(destDir, destName); let content = fs.readFileSync(srcPath, 'utf8'); - const claudeDirRegex = /~\/\.claude\//g; + const globalClaudeRegex = /~\/\.claude\//g; + const localClaudeRegex = /\.\/\.claude\//g; const opencodeDirRegex = /~\/\.opencode\//g; - content = content.replace(claudeDirRegex, pathPrefix); + content = content.replace(globalClaudeRegex, pathPrefix); + content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`); content = content.replace(opencodeDirRegex, pathPrefix); content = processAttribution(content, getCommitAttribution(runtime)); content = convertClaudeToOpencodeFrontmatter(content); @@ -665,10 +688,12 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) { if (entry.isDirectory()) { copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime); } else if (entry.name.endsWith('.md')) { - // Always replace ~/.claude/ as it is the source of truth in the repo + // Replace ~/.claude/ and ./.claude/ with runtime-appropriate paths let content = fs.readFileSync(srcPath, 'utf8'); - const claudeDirRegex = /~\/\.claude\//g; - content = content.replace(claudeDirRegex, pathPrefix); + const globalClaudeRegex = /~\/\.claude\//g; + const localClaudeRegex = /\.\/\.claude\//g; + content = content.replace(globalClaudeRegex, pathPrefix); + content = content.replace(localClaudeRegex, `./${dirName}/`); content = processAttribution(content, getCommitAttribution(runtime)); // Convert frontmatter for opencode compatibility @@ -1429,16 +1454,25 @@ function install(isGlobal, runtime = 'claude') { console.log(` ${green}✓${reset} Wrote package.json (CommonJS mode)`); // Copy hooks from dist/ (bundled with dependencies) + // Template paths for the target runtime (replaces '.claude' with correct config dir) const hooksSrc = path.join(src, 'hooks', 'dist'); if (fs.existsSync(hooksSrc)) { const hooksDest = path.join(targetDir, 'hooks'); fs.mkdirSync(hooksDest, { recursive: true }); const hookEntries = fs.readdirSync(hooksSrc); + const configDirReplacement = getConfigDirFromHome(runtime, isGlobal); for (const entry of hookEntries) { const srcFile = path.join(hooksSrc, entry); if (fs.statSync(srcFile).isFile()) { const destFile = path.join(hooksDest, entry); - fs.copyFileSync(srcFile, destFile); + // Template .js files to replace '.claude' with runtime-specific config dir + if (entry.endsWith('.js')) { + let content = fs.readFileSync(srcFile, 'utf8'); + content = content.replace(/'\.claude'/g, configDirReplacement); + fs.writeFileSync(destFile, content); + } else { + fs.copyFileSync(srcFile, destFile); + } } } if (verifyInstalled(hooksDest, 'hooks')) { diff --git a/commands/gsd/reapply-patches.md b/commands/gsd/reapply-patches.md index 4f1260d70..f6eb8107b 100644 --- a/commands/gsd/reapply-patches.md +++ b/commands/gsd/reapply-patches.md @@ -14,11 +14,11 @@ After a GSD update wipes and reinstalls files, this command merges user's previo Check for local patches directory: ```bash -# Global install -PATCHES_DIR="${HOME}/.claude/gsd-local-patches" +# Global install (path templated at install time) +PATCHES_DIR=~/.claude/gsd-local-patches # Local install fallback if [ ! -d "$PATCHES_DIR" ]; then - PATCHES_DIR="./.claude/gsd-local-patches" + PATCHES_DIR=./.claude/gsd-local-patches fi ``` diff --git a/get-shit-done/workflows/update.md b/get-shit-done/workflows/update.md index 84e08462e..4903924b9 100644 --- a/get-shit-done/workflows/update.md +++ b/get-shit-done/workflows/update.md @@ -13,8 +13,9 @@ Detect whether GSD is installed locally or globally by checking both locations: ```bash # Check local first (takes priority) -if [ -f "./.claude/get-shit-done/VERSION" ]; then - cat "./.claude/get-shit-done/VERSION" +# Paths templated at install time for runtime compatibility +if [ -f ./.claude/get-shit-done/VERSION ]; then + cat ./.claude/get-shit-done/VERSION echo "LOCAL" elif [ -f ~/.claude/get-shit-done/VERSION ]; then cat ~/.claude/get-shit-done/VERSION @@ -167,6 +168,7 @@ rm -f ./.claude/cache/gsd-update-check.json ```bash rm -f ~/.claude/cache/gsd-update-check.json ``` +(Paths are templated at install time for runtime compatibility)