The flattened install layout broke the third-party capability ecosystem in two
ways. Both are fixed at the host-version / installer boundary.
Gap 1 — host version read as 0.0.0. The running GSD version was resolved via
require('../../../package.json') (loader/source) and require('../../package.json')
(the gsd-tools CLI), which in the installed layout is the versionless CommonJS
marker → the fail-closed fallback reported 0.0.0, so `capability install` rejected
any manifest with a real engines.gsd range as "incompatible with GSD 0.0.0". For
runtimes that get no marker, and for local installs, that walked-up package.json
could even be the USER's own project, reporting a wrong version. Fix:
readHostVersion() (capability-loader.cts, capability-source.cts) and capHostVersion()
(gsd-tools.cjs) now prefer the authoritative gsd-core/VERSION the installer already
writes for EVERY runtime (mirrors resolveVersionFrom(), #1383), falling back to the
runtime-root package.json for the dev/source tree, then fail-closing. This fixes
every runtime and the actual `capability install` CLI path without touching the
marker package.json, so uninstall is unchanged (no data-loss surface).
Gap 2 — scripts/gen-capability-registry.cjs (+ its sibling
gen-loop-host-contract.cjs) were never copied by the installer, so the loader's
never-crash invariant discarded every overlay and fell back to the frozen
first-party registry (installed capabilities silently inert). Now copied,
uninstalled, and manifest-tracked exactly like fix-slash-commands.cjs (#1223).
Regenerates the 16 golden-install-parity fixtures to capture the two newly shipped
generator scripts and the gsd-tools.cjs change.
Regression tests (RED→GREEN): readHostVersion VERSION-first / fallback / fail-closed
resolution; an end-to-end `capability install` against a REAL installed layout
proving the engines gate sees the real host version, not 0.0.0; and a real-install
check that both generators are shipped and manifest-tracked.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7377,6 +7377,11 @@ function uninstall(isGlobal, runtime = 'claude') {
|
||||
const fixSlashUninstallPath = path.join(targetDir, 'scripts', 'fix-slash-commands.cjs');
|
||||
try { fs.unlinkSync(fixSlashUninstallPath); } catch (_) { /* best-effort */ }
|
||||
|
||||
// Remove the capability registry generator scripts (#1920) — before the scripts/ rmdir
|
||||
for (const gen of ['gen-capability-registry.cjs', 'gen-loop-host-contract.cjs']) {
|
||||
try { fs.unlinkSync(path.join(targetDir, 'scripts', gen)); } catch (_) { /* best-effort */ }
|
||||
}
|
||||
|
||||
// If scripts/ dir is now empty, remove it too
|
||||
const scriptsUninstallDir = path.join(targetDir, 'scripts');
|
||||
if (fs.existsSync(scriptsUninstallDir)) {
|
||||
@@ -8100,6 +8105,15 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
||||
manifest.files['scripts/fix-slash-commands.cjs'] = fileHash(fixSlashInstallPath);
|
||||
}
|
||||
|
||||
// Track the capability registry generator scripts (#1920) — top-level scripts/ files
|
||||
// not covered by the changeset/lib loops.
|
||||
for (const gen of ['gen-capability-registry.cjs', 'gen-loop-host-contract.cjs']) {
|
||||
const genInstallPath = path.join(configDir, 'scripts', gen);
|
||||
if (fs.existsSync(genInstallPath)) {
|
||||
manifest.files['scripts/' + gen] = fileHash(genInstallPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Track the OpenCode native plugin adapter (#1914) so update/drift detection
|
||||
// and uninstall can account for it.
|
||||
if (isOpencode) {
|
||||
@@ -9629,6 +9643,31 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy scripts/gen-capability-registry.cjs + scripts/gen-loop-host-contract.cjs —
|
||||
// required by gsd-core/bin/lib/capability-loader.cjs at overlay-composition time via
|
||||
// require('../../../scripts/gen-capability-registry.cjs') (which itself requires
|
||||
// gen-loop-host-contract.cjs). Without these, the loader's never-crash invariant
|
||||
// discards EVERY third-party capability overlay and silently falls back to the frozen
|
||||
// first-party registry, so installed capabilities are inert (#1920). Same class of
|
||||
// gap as #1223 (fix-slash-commands.cjs) and copied unconditionally for the same reason:
|
||||
// any runtime that installs gsd-core/ needs the capability system to compose.
|
||||
{
|
||||
const capGenDestDir = path.join(targetDir, 'scripts');
|
||||
fs.mkdirSync(capGenDestDir, { recursive: true });
|
||||
for (const gen of ['gen-capability-registry.cjs', 'gen-loop-host-contract.cjs']) {
|
||||
const genSrc = path.join(src, 'scripts', gen);
|
||||
const genDest = path.join(capGenDestDir, gen);
|
||||
if (!fs.existsSync(genSrc)) {
|
||||
failures.push(`scripts/${gen} (source missing from package — reinstall from npm)`);
|
||||
} else {
|
||||
fs.copyFileSync(genSrc, genDest);
|
||||
if (!verifyFileInstalled(genDest, `scripts/${gen}`)) {
|
||||
failures.push(`scripts/${gen}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove legacy get-shit-done-cc artifacts and stale update caches (#607).
|
||||
// cleanupLegacyGsdCc handles both the legacy shared cache and the per-package
|
||||
// cache (formerly an inline unlinkSync here). A cleanup failure must never
|
||||
|
||||
Reference in New Issue
Block a user