feat(#607): rebuild get-shit-done-cc → gsd-core migration (per-package cache + installer auto-cleanup + --dry-run) (#611)
* feat(#607): rebuild get-shit-done-cc → gsd-core migration Leftover get-shit-done-cc installs poisoned the shared update cache, causing a permanent false "update available". Rebuild the migration so a stale old install is both harmless and actively removed. - Per-package update cache filename (gsd-update-check-<slug>.json) in the shared ~/.cache/gsd dir, single-sourced via package-identity; writers stamp package_name and readers reject foreign/absent lineage. Multi- runtime visibility preserved (same shared dir + filename across runtimes). - New get-shit-done/bin/lib/legacy-cleanup.cjs seam: detects code-file references to the old package + the legacy fixed-name cache across home runtime dirs; installer auto-cleans on every install; --dry-run previews and mutates nothing. User hooks and dev-preferences are never touched. - update.md cache-clear globs gsd-update-check*.json across ALL supported runtimes (adds cursor/windsurf/augment/trae/qwen/hermes/codebuddy/cline). - Fix worker MODULE_NOT_FOUND post-install (ship managed-hooks-registry.cjs + degrade gracefully) so the per-package cache is always written. - Diataxis how-to: docs/cleanup-get-shit-done-cc.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#607): add changeset for PR #611 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#607): set USERPROFILE alongside HOME in dry-run install test for Windows os.homedir() reads USERPROFILE on win32, so HOME-only isolation let the spawned installer scan the real runner home on windows-latest. Set both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { isSemverNewer } = require('../get-shit-done/bin/lib/semver-compare.cjs');
|
||||
const { PACKAGE_NAME, updateCacheFileName } = require('../get-shit-done/bin/lib/package-identity.cjs');
|
||||
|
||||
// --- Config + last-command readers ------------------------------------------
|
||||
|
||||
@@ -395,32 +396,22 @@ function runStatusline() {
|
||||
const gsdStateStr = task ? '' : formatGsdState(readGsdState(dir) || {});
|
||||
|
||||
// GSD update available?
|
||||
// Check shared cache first (#1421), fall back to runtime-specific cache for
|
||||
// backward compatibility with older gsd-check-update.js versions.
|
||||
// Read only the per-package shared cache file (#607). The legacy
|
||||
// runtime-specific fallback has been removed — the per-package filename
|
||||
// carries lineage and avoids multi-runtime resolution mismatches (#1421).
|
||||
let gsdUpdate = '';
|
||||
const sharedCacheFile = path.join(homeDir, '.cache', 'gsd', 'gsd-update-check.json');
|
||||
const legacyCacheFile = path.join(claudeDir, 'cache', 'gsd-update-check.json');
|
||||
const cacheFile = fs.existsSync(sharedCacheFile) ? sharedCacheFile : legacyCacheFile;
|
||||
const cacheFile = path.join(homeDir, '.cache', 'gsd', updateCacheFileName);
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
try {
|
||||
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
||||
if (cache.update_available) {
|
||||
const { showUpdate, staleWarning } = evaluateUpdateCache(cache);
|
||||
if (showUpdate) {
|
||||
gsdUpdate = '\x1b[33m⬆ /gsd:update\x1b[0m │ ';
|
||||
}
|
||||
if (cache.stale_hooks && cache.stale_hooks.length > 0) {
|
||||
// If installed version is ahead of npm latest, this is a dev install.
|
||||
// Running /gsd:update would downgrade — show a contextual warning instead.
|
||||
const isDevInstall = (
|
||||
cache.installed &&
|
||||
cache.latest &&
|
||||
cache.latest !== 'unknown' &&
|
||||
isInstalledAheadOfLatest(cache.installed, cache.latest)
|
||||
);
|
||||
if (isDevInstall) {
|
||||
gsdUpdate += '\x1b[33m⚠ dev install — re-run installer to sync hooks\x1b[0m │ ';
|
||||
} else {
|
||||
gsdUpdate += '\x1b[31m⚠ stale hooks — run /gsd:update\x1b[0m │ ';
|
||||
}
|
||||
if (staleWarning === 'dev') {
|
||||
gsdUpdate += '\x1b[33m⚠ dev install — re-run installer to sync hooks\x1b[0m │ ';
|
||||
} else if (staleWarning === 'stale') {
|
||||
gsdUpdate += '\x1b[31m⚠ stale hooks — run /gsd:update\x1b[0m │ ';
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -507,12 +498,39 @@ function isInstalledAheadOfLatest(installed, latest) {
|
||||
return isSemverNewer(installed, latest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure function: evaluate an update-check cache object and return display flags.
|
||||
* Applies lineage guard — if package_name is absent or foreign, treats cache as absent.
|
||||
*
|
||||
* @param {object|null} cache Parsed cache object, or null.
|
||||
* @returns {{ showUpdate: boolean, staleWarning: 'none'|'dev'|'stale' }}
|
||||
*/
|
||||
function evaluateUpdateCache(cache) {
|
||||
const none = { showUpdate: false, staleWarning: 'none' };
|
||||
if (!cache) return none;
|
||||
// Lineage guard: package_name must be present and match this package.
|
||||
if (!cache.package_name || cache.package_name !== PACKAGE_NAME) return none;
|
||||
const showUpdate = Boolean(cache.update_available);
|
||||
let staleWarning = 'none';
|
||||
if (cache.stale_hooks && cache.stale_hooks.length > 0) {
|
||||
const isDevInstall = (
|
||||
cache.installed &&
|
||||
cache.latest &&
|
||||
cache.latest !== 'unknown' &&
|
||||
isInstalledAheadOfLatest(cache.installed, cache.latest)
|
||||
);
|
||||
staleWarning = isDevInstall ? 'dev' : 'stale';
|
||||
}
|
||||
return { showUpdate, staleWarning };
|
||||
}
|
||||
|
||||
// Export helpers for unit tests. Harmless when run as a script.
|
||||
module.exports = {
|
||||
readGsdState, parseStateMd, formatGsdState,
|
||||
readGsdConfig, getConfigValue, readLastSlashCommand,
|
||||
composeStatusline,
|
||||
isInstalledAheadOfLatest,
|
||||
evaluateUpdateCache,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user