* 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>
109 lines
4.8 KiB
JavaScript
109 lines
4.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// gsd-hook-version: {{GSD_VERSION}}
|
|
// Background worker spawned by gsd-check-update.js (SessionStart hook).
|
|
// Checks for GSD updates and stale hooks, writes result to cache file.
|
|
// Receives paths via environment variables set by the parent hook.
|
|
//
|
|
// Using a separate file (rather than node -e '<inline code>') avoids the
|
|
// template-literal regex-escaping problem: regex source is plain JS here.
|
|
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { isSemverNewer } = require('../get-shit-done/bin/lib/semver-compare.cjs');
|
|
// Latest-version lookup is delegated to the single deterministic adapter
|
|
// (#498). checkLatestVersion() owns the npm-view call, the timeout/semver
|
|
// policy, and the package name — sourced from the baked Package Identity seam.
|
|
// The previous `require('../package.json').name` (#378) resolved to undefined
|
|
// in the installed tree (only a {"type":"commonjs"} marker ships), so the
|
|
// background check never reported updates.
|
|
const { checkLatestVersion } = require('../get-shit-done/bin/check-latest-version.cjs');
|
|
const { PACKAGE_NAME } = require('../get-shit-done/bin/lib/package-identity.cjs');
|
|
// Authoritative list of managed hooks — shared with tests to retire source-grep
|
|
// assertions (pending-migration-to-typed-ir [#455]).
|
|
// NOTE: managed-hooks-registry.cjs must be in HOOKS_TO_COPY (scripts/build-hooks.js)
|
|
// so it is present in hooks/dist/ and ships to the installed runtime hooks/ dir.
|
|
// If it is missing (e.g., installed from an older dist), catch and degrade gracefully
|
|
// so the worker always proceeds to compute and write the result cache record.
|
|
let MANAGED_HOOKS = [];
|
|
try {
|
|
({ MANAGED_HOOKS } = require('./managed-hooks-registry.cjs'));
|
|
} catch (e) {
|
|
// Module not found in installed runtime — stale-hook detection degrades to
|
|
// no-op (empty list means no hooks are checked for staleness). The worker
|
|
// still runs and writes package_name / installed / latest / update_available.
|
|
}
|
|
|
|
const cacheFile = process.env.GSD_CACHE_FILE;
|
|
const projectVersionFile = process.env.GSD_PROJECT_VERSION_FILE;
|
|
const globalVersionFile = process.env.GSD_GLOBAL_VERSION_FILE;
|
|
|
|
// Check project directory first (local install), then global
|
|
let installed = '0.0.0';
|
|
let configDir = '';
|
|
try {
|
|
if (fs.existsSync(projectVersionFile)) {
|
|
installed = fs.readFileSync(projectVersionFile, 'utf8').trim();
|
|
configDir = path.dirname(path.dirname(projectVersionFile));
|
|
} else if (fs.existsSync(globalVersionFile)) {
|
|
installed = fs.readFileSync(globalVersionFile, 'utf8').trim();
|
|
configDir = path.dirname(path.dirname(globalVersionFile));
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Check for stale hooks — compare hook version headers against installed VERSION
|
|
// Hooks are installed at configDir/hooks/ (e.g. ~/.claude/hooks/) (#1421)
|
|
// Only check hooks that GSD currently ships — orphaned files from removed features
|
|
// (e.g., gsd-intel-*.js) must be ignored to avoid permanent stale warnings (#1750)
|
|
// MANAGED_HOOKS is imported from ./managed-hooks-registry.cjs above.
|
|
|
|
let staleHooks = [];
|
|
if (configDir) {
|
|
const hooksDir = path.join(configDir, 'hooks');
|
|
try {
|
|
if (fs.existsSync(hooksDir)) {
|
|
const hookFiles = fs.readdirSync(hooksDir).filter(f => MANAGED_HOOKS.includes(f));
|
|
for (const hookFile of hookFiles) {
|
|
try {
|
|
const content = fs.readFileSync(path.join(hooksDir, hookFile), 'utf8');
|
|
// Match both JS (//) and bash (#) comment styles
|
|
const versionMatch = content.match(/(?:\/\/|#) gsd-hook-version:\s*(.+)/);
|
|
if (versionMatch) {
|
|
const hookVersion = versionMatch[1].trim();
|
|
if (isSemverNewer(installed, hookVersion) && !hookVersion.includes('{{')) {
|
|
staleHooks.push({ file: hookFile, hookVersion, installedVersion: installed });
|
|
}
|
|
} else {
|
|
// No version header at all — definitely stale (pre-version-tracking)
|
|
staleHooks.push({ file: hookFile, hookVersion: 'unknown', installedVersion: installed });
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
// Single adapter for the registry lookup (#498). checkLatestVersion() routes
|
|
// through the shell-projection seam, which already owns the Windows shell-flag
|
|
// policy, the timeout, and semver validation. A non-ok result leaves latest
|
|
// null, exactly as the previous inline try/catch did.
|
|
let latest = null;
|
|
try {
|
|
const lv = checkLatestVersion();
|
|
if (lv && lv.ok) latest = lv.version;
|
|
} catch (e) {}
|
|
|
|
const result = {
|
|
update_available: latest && isSemverNewer(latest, installed),
|
|
installed,
|
|
latest: latest || 'unknown',
|
|
checked: Math.floor(Date.now() / 1000),
|
|
stale_hooks: staleHooks.length > 0 ? staleHooks : undefined,
|
|
package_name: PACKAGE_NAME,
|
|
};
|
|
|
|
if (cacheFile) {
|
|
try { fs.writeFileSync(cacheFile, JSON.stringify(result)); } catch (e) {}
|
|
}
|