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.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 <claudeDir>/gsd-core/bin/lib, which has no
|
||||
// commands/gsd ancestor (global skills layout). Only the marker rescues it.
|
||||
|
||||
Reference in New Issue
Block a user