From ddfc6b08151d3a1f35fcf86113b0ddfed9ad8e9c Mon Sep 17 00:00:00 2001 From: Joe Slitzker Date: Thu, 25 Jun 2026 09:46:09 -0500 Subject: [PATCH] fix(#1477): scope changeset to marker writer; lock guard with negative tests Address maintainer re-review nits: - Changeset body no longer claims install-exports resolution (Failure 2 is now handled upstream by #1511 / ADR-1508 Phase 2). This PR delivers the .gsd-source marker writer, which fixes list/status (Failure 1). - Add negative tests locking the `runtime === 'claude' && isGlobal` write guard: a non-claude global install and a claude *local* install both provision no marker. Verified must-fail-first against a relaxed guard. --- .changeset/brave-hawks-fly.md | 2 +- tests/runtime-artifact-layout.test.cjs | 43 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/.changeset/brave-hawks-fly.md b/.changeset/brave-hawks-fly.md index a2f75e972..b07b243e8 100644 --- a/.changeset/brave-hawks-fly.md +++ b/.changeset/brave-hawks-fly.md @@ -2,4 +2,4 @@ type: Fixed pr: 1487 --- -**`/gsd-surface` works on Claude Code global installs** — the installer now writes a `.gsd-source` marker and install-exports resolve in the deployed layout, so `list`/`status` and `profile`/`enable`/`disable`/`reset` no longer throw `could not locate commands/gsd` or `MODULE_NOT_FOUND`. (#1487) +**`/gsd-surface` (`list`/`status`) works on Claude Code global installs** — the installer now writes a `.gsd-source` marker pointing at its `commands/gsd` source, so `findInstallSourceRoot` resolves on the global skills layout (which ships no `commands/gsd` tree) instead of throwing `could not locate commands/gsd`. (#1487) diff --git a/tests/runtime-artifact-layout.test.cjs b/tests/runtime-artifact-layout.test.cjs index 74e9f69e6..01680385d 100644 --- a/tests/runtime-artifact-layout.test.cjs +++ b/tests/runtime-artifact-layout.test.cjs @@ -726,6 +726,49 @@ describe('#1477 .gsd-source marker provisioning', () => { assert.equal(path.basename(path.dirname(markerSrc)), 'commands'); }); + // ── Guard: marker is scoped to claude-global ONLY (#1477 PR-scope) ─────────── + // Locks the `runtime === 'claude' && isGlobal` write guard. Every other layout + // (non-claude runtimes, and claude *local*) ships a commands/gsd source tree, so + // findInstallSourceRoot's walk-up already resolves and the marker must not appear. + function findGsdSourceMarkers(root) { + const found = []; + const walk = (dir) => { + let entries; + try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; } + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) walk(full); + else if (e.name === '.gsd-source') found.push(full); + } + }; + walk(root); + return found; + } + + test('non-claude global install writes no .gsd-source marker (locks runtime === claude)', () => { + runInstall(true /* isGlobal */, 'cursor'); + assert.deepEqual( + findGsdSourceMarkers(tmpRoot), [], + 'a non-claude runtime must not provision the claude-global marker', + ); + }); + + test('claude local install writes no .gsd-source marker (locks isGlobal)', () => { + // Local installs target process.cwd()/.claude — chdir into the fixture so the + // install is contained within tmpRoot rather than polluting the repo. + const savedCwd = process.cwd(); + process.chdir(tmpRoot); + try { + runInstall(false /* isGlobal */, 'claude'); + } finally { + process.chdir(savedCwd); + } + assert.deepEqual( + findGsdSourceMarkers(tmpRoot), [], + 'a claude local install must not provision the marker — local ships commands/gsd', + ); + }); + // ── Failure 1 end-to-end: resolution succeeds FROM the deployed tree ───────── // The deployed module's __dirname is /gsd-core/bin/lib, which has no // commands/gsd ancestor (global skills layout). Only the marker rescues it.