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:
@@ -1265,26 +1265,62 @@ describe('shared cache directory (#1421)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('gsd-statusline.js checks shared cache first, falls back to legacy (#1421)', () => {
|
||||
const content = fs.readFileSync(
|
||||
test('gsd-statusline.js reads the per-package shared cache and rejects foreign lineage (#1421/#607)', () => {
|
||||
const { evaluateUpdateCache } = require('../hooks/gsd-statusline.js');
|
||||
const { updateCacheFileName, PACKAGE_NAME } = require('../get-shit-done/bin/lib/package-identity.cjs');
|
||||
|
||||
// Per-package filename embeds the package identity — no generic fallback
|
||||
assert.strictEqual(
|
||||
updateCacheFileName,
|
||||
'gsd-update-check-opengsd-gsd-core.json',
|
||||
'updateCacheFileName must be the per-package filename'
|
||||
);
|
||||
|
||||
// The statusline must NOT reference a legacyCacheFile — the legacy fallback was removed
|
||||
// allow-test-rule: architectural-invariant
|
||||
const statuslineSrc = fs.readFileSync(
|
||||
path.join(__dirname, '..', 'hooks', 'gsd-statusline.js'), 'utf-8'
|
||||
);
|
||||
// Statusline must check the shared cache path first
|
||||
assert.ok(
|
||||
content.includes("path.join(homeDir, '.cache', 'gsd', 'gsd-update-check.json')"),
|
||||
'statusline must check shared cache at ~/.cache/gsd/gsd-update-check.json'
|
||||
!statuslineSrc.includes('legacyCacheFile'),
|
||||
'gsd-statusline.js must not reference legacyCacheFile — legacy fallback was removed in #607'
|
||||
);
|
||||
// Must fall back to legacy runtime-specific cache for backward compat
|
||||
assert.ok(
|
||||
content.includes("path.join(claudeDir, 'cache', 'gsd-update-check.json')"),
|
||||
'statusline must fall back to legacy cache at claudeDir/cache/gsd-update-check.json'
|
||||
statuslineSrc.includes(updateCacheFileName) || statuslineSrc.includes('updateCacheFileName'),
|
||||
'gsd-statusline.js must reference the per-package updateCacheFileName'
|
||||
);
|
||||
// Shared cache must be checked before legacy (existsSync order matters)
|
||||
const sharedIdx = content.indexOf('sharedCacheFile');
|
||||
const legacyIdx = content.indexOf('legacyCacheFile');
|
||||
assert.ok(
|
||||
sharedIdx < legacyIdx,
|
||||
'shared cache must be defined and checked before legacy cache'
|
||||
|
||||
// evaluateUpdateCache: foreign package_name → no update shown
|
||||
assert.deepStrictEqual(
|
||||
evaluateUpdateCache({ package_name: 'other-package', update_available: true }),
|
||||
{ showUpdate: false, staleWarning: 'none' },
|
||||
'foreign package_name must be rejected (lineage guard)'
|
||||
);
|
||||
|
||||
// evaluateUpdateCache: absent package_name → no update shown
|
||||
assert.deepStrictEqual(
|
||||
evaluateUpdateCache({ update_available: true }),
|
||||
{ showUpdate: false, staleWarning: 'none' },
|
||||
'absent package_name must be rejected (lineage guard)'
|
||||
);
|
||||
|
||||
// evaluateUpdateCache: null cache → no update shown
|
||||
assert.deepStrictEqual(
|
||||
evaluateUpdateCache(null),
|
||||
{ showUpdate: false, staleWarning: 'none' },
|
||||
'null cache must return no-update'
|
||||
);
|
||||
|
||||
// evaluateUpdateCache: matching package_name + update_available:true → show update
|
||||
const result = evaluateUpdateCache({ package_name: PACKAGE_NAME, update_available: true });
|
||||
assert.strictEqual(result.showUpdate, true,
|
||||
'matching package_name with update_available:true must set showUpdate=true'
|
||||
);
|
||||
|
||||
// evaluateUpdateCache: matching package_name + update_available:false → no update
|
||||
const noUpdate = evaluateUpdateCache({ package_name: PACKAGE_NAME, update_available: false });
|
||||
assert.strictEqual(noUpdate.showUpdate, false,
|
||||
'matching package_name with update_available:false must not show update'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user