refactor: hard-fork GSD -> MSD (Make Software Done)

Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
This commit is contained in:
Jakub Zych
2026-10-06 01:47:40 +02:00
parent fe069b2a56
commit a9a7a328e6
2763 changed files with 78465 additions and 78434 deletions

View File

@@ -7,24 +7,24 @@
* `<configRoot>/package.json` unconditionally — no existence check, no merge,
* no backup. On OpenCode and Kilo that file is documented, user-writable
* territory (it is where local-plugin npm dependencies are declared), so every
* install and every `/gsd-update` destroyed the user's `name`, `type`,
* install and every `/msd-update` destroyed the user's `name`, `type`,
* `dependencies`, and `scripts`.
*
* The uninstall path had always read the file first and unlinked it only on an
* exact content match. The defect was that asymmetry: the discipline existed,
* it just was not applied on the write side.
*
* The fix moves the marker into the directories GSD actually fills with its own
* The fix moves the marker into the directories MSD actually fills with its own
* `.js` files — `hooks/` and the `nativePlugin.dir` — and routes install and
* uninstall through one shared ownership predicate. These tests pin both
* halves: the config root is never written, and a user-authored package.json is
* never overwritten even where GSD does write.
* never overwritten even where MSD does write.
*
* Coverage maps to the issue's acceptance criteria:
* AC1 — a user-authored config-root package.json survives a fresh install
* AC2 — it survives a second install (the `/gsd-update` re-install path)
* AC3 — GSD's staged .js files still resolve as CommonJS
* AC4 — uninstall removes only markers GSD wrote
* AC2 — it survives a second install (the `/msd-update` re-install path)
* AC3 — MSD's staged .js files still resolve as CommonJS
* AC4 — uninstall removes only markers MSD wrote
*/
const { test, describe, before } = require('node:test');
@@ -43,7 +43,7 @@ const {
classifyMarker,
ensureCommonJsMarker,
removeCommonJsMarker,
} = require('../gsd-core/bin/lib/commonjs-marker.cjs');
} = require('../msd-core/bin/lib/commonjs-marker.cjs');
const INSTALL_SCRIPT = path.join(__dirname, '..', 'bin', 'install.js');
const BUILD_SCRIPT = path.join(__dirname, '..', 'scripts', 'build-hooks.js');
@@ -70,12 +70,12 @@ function mkTmp(prefix) {
* Run the real installer against a throwaway config root.
*
* HOME/USERPROFILE/CLAUDE_CONFIG_DIR are all redirected into the temp tree so
* the installer can never reach the developer's live profile — gsd-core's
* the installer can never reach the developer's live profile — msd-core's
* installer resolves through exactly those variables.
*/
function runInstall(root, runtime, extraArgs = []) {
const env = { ...process.env, HOME: root, USERPROFILE: root, CLAUDE_CONFIG_DIR: root };
delete env.GSD_TEST_MODE;
delete env.MSD_TEST_MODE;
const result = runNode(
[INSTALL_SCRIPT, `--${runtime}`, '--global', '--config-dir', root, ...extraArgs],
{ cwd: root, env, timeoutMs: INSTALL_TIMEOUT_MS },
@@ -89,14 +89,14 @@ function runInstall(root, runtime, extraArgs = []) {
}
describe('commonjs-marker: ownership predicate', () => {
test('classifies absent, GSD-owned, and foreign package.json files', (t) => {
test('classifies absent, MSD-owned, and foreign package.json files', (t) => {
const dir = mkTmp('cjs-marker-classify-');
t.after(() => cleanup(dir));
assert.equal(classifyMarker(dir), 'absent');
fs.writeFileSync(path.join(dir, 'package.json'), `${COMMONJS_MARKER}\n`);
assert.equal(classifyMarker(dir), 'gsd-owned');
assert.equal(classifyMarker(dir), 'msd-owned');
fs.writeFileSync(path.join(dir, 'package.json'), USER_PACKAGE_JSON);
assert.equal(classifyMarker(dir), 'foreign');
@@ -130,12 +130,12 @@ describe('commonjs-marker: ownership predicate', () => {
// A DANGLING symlink is the dangerous case: existsSync() reports false for
// it, so an existsSync-based guard would classify `absent` and then write
// straight through the link, landing outside the directory GSD owns.
// straight through the link, landing outside the directory MSD owns.
fs.symlinkSync(outside, link);
assert.equal(classifyMarker(owned), 'foreign');
assert.equal(ensureCommonJsMarker(owned), 'preserved-foreign');
assert.ok(!fs.existsSync(outside), 'the write must not follow the symlink out of the directory');
assert.equal(removeCommonJsMarker(owned), false, 'a symlink is never GSD-owned');
assert.equal(removeCommonJsMarker(owned), false, 'a symlink is never MSD-owned');
assert.ok(fs.lstatSync(link).isSymbolicLink(), 'the symlink itself must survive');
});
@@ -148,12 +148,12 @@ describe('commonjs-marker: ownership predicate', () => {
// (cursor/windsurf/codex). That copy probed with `fs.existsSync`, which
// follows symlinks and reports false for a DANGLING one — so it classified
// the link `absent` and wrote straight through it, landing outside the
// directory GSD owns. #2544 makes it delegate to commonjs-marker instead.
// directory MSD owns. #2544 makes it delegate to commonjs-marker instead.
//
// This asserts the hardening reaches that path, not just the module: it is
// the only coverage that fails if the duplicate is ever reintroduced, since
// the two implementations agree on every non-adversarial input.
const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
const hooksSurface = require('../msd-core/bin/lib/runtime-hooks-surface.cjs');
const outside = path.join(dir, 'escaped.json');
const owned = path.join(dir, 'hooks');
@@ -163,29 +163,29 @@ describe('commonjs-marker: ownership predicate', () => {
assert.equal(
hooksSurface.ensureCommonJsMarker(owned),
false,
'a dangling symlink is not GSD-owned, so the marker must not be reported present',
'a dangling symlink is not MSD-owned, so the marker must not be reported present',
);
assert.ok(
!fs.existsSync(outside),
'the write must not follow the symlink out of the hooks directory',
);
assert.equal(
hooksSurface.removeCommonJsMarkerIfGsdOwned(owned),
hooksSurface.removeCommonJsMarkerIfMsdOwned(owned),
false,
'a symlink is never GSD-owned, so uninstall must not remove it',
'a symlink is never MSD-owned, so uninstall must not remove it',
);
});
// The #2717 writers mkdir hooks/ unconditionally, then staged their scripts
// conditionally on the source existing — so with an empty hooks source they
// created a directory, filled it with nothing, and marked it as GSD's anyway.
// That is the same write-into-territory-GSD-did-not-fill this issue is about,
// created a directory, filled it with nothing, and marked it as MSD's anyway.
// That is the same write-into-territory-MSD-did-not-fill this issue is about,
// and it is what `stagedHooks` guards on the shared-bundle path. These pin the
// matching gate on the two dedicated writers.
for (const rt of ['cursor', 'windsurf']) {
test(`${rt}: staging zero hook scripts leaves hooks/ marker-free`, (t) => {
const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
const root = mkTmp(`gsd-2544-${rt}-nostage-`);
const hooksSurface = require('../msd-core/bin/lib/runtime-hooks-surface.cjs');
const root = mkTmp(`msd-2544-${rt}-nostage-`);
t.after(() => cleanup(root));
// A src tree whose hooks/ dir exists but holds none of the runtime's
@@ -212,7 +212,7 @@ describe('commonjs-marker: ownership predicate', () => {
});
}
test('removeCommonJsMarker removes only GSD-owned markers', (t) => {
test('removeCommonJsMarker removes only MSD-owned markers', (t) => {
const dir = mkTmp('cjs-marker-remove-');
t.after(() => cleanup(dir));
const target = path.join(dir, 'package.json');
@@ -277,7 +277,7 @@ describe('commonjs-marker: fault injection', () => {
fs.writeFileSync(path.join(dir, 'package.json'), `${COMMONJS_MARKER}\n`);
// Present-but-unreadable never downgrades to the permissive answer — the
// file's bytes are exactly GSD's marker, and it must STILL classify foreign
// file's bytes are exactly MSD's marker, and it must STILL classify foreign
// because we could not prove it.
const result = withPatched('readFileSync', () => { throw errWith('EACCES'); },
() => classifyMarker(dir));
@@ -360,7 +360,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
for (const runtime of ['opencode', 'claude']) {
test(`${runtime}: a user-authored package.json survives install and re-install`, (t) => {
const root = mkTmp(`gsd-2544-${runtime}-`);
const root = mkTmp(`msd-2544-${runtime}-`);
t.after(() => cleanup(root));
const userPkg = path.join(root, 'package.json');
fs.writeFileSync(userPkg, USER_PACKAGE_JSON);
@@ -374,7 +374,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
'fresh install must not modify the user-authored config-root package.json',
);
// AC2 — the /gsd-update re-install path leaves it untouched too.
// AC2 — the /msd-update re-install path leaves it untouched too.
runInstall(root, runtime);
assert.equal(
sha256(fs.readFileSync(userPkg)),
@@ -389,8 +389,8 @@ describe('#2544 regression: install must not clobber the config-root package.jso
assert.deepEqual(parsed.dependencies, { shescape: '^2.1.0', zod: '^3.23.8' });
assert.deepEqual(parsed.scripts, { postinstall: 'echo user-owned' });
// AC3 — GSD's own staged scripts still get a CommonJS marker, from the
// directory GSD owns, so `require` keeps working under "type": "module".
// AC3 — MSD's own staged scripts still get a CommonJS marker, from the
// directory MSD owns, so `require` keeps working under "type": "module".
const hooksMarker = path.join(root, 'hooks', 'package.json');
assert.ok(fs.existsSync(hooksMarker), 'hooks/package.json marker must be staged');
assert.equal(JSON.parse(fs.readFileSync(hooksMarker, 'utf8')).type, 'commonjs');
@@ -398,10 +398,10 @@ describe('#2544 regression: install must not clobber the config-root package.jso
}
test('staged hook helpers still load as CommonJS under a "type": "module" config root', (t) => {
const root = mkTmp('gsd-2544-esm-');
const root = mkTmp('msd-2544-esm-');
t.after(() => cleanup(root));
// The config root declares ESM — the exact shape that breaks Node's
// walk-up resolution for GSD's staged .js files.
// walk-up resolution for MSD's staged .js files.
fs.writeFileSync(path.join(root, 'package.json'), USER_PACKAGE_JSON);
runInstall(root, 'opencode');
@@ -423,32 +423,32 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('opencode: the native plugin dir gets its own marker', (t) => {
const root = mkTmp('gsd-2544-plugin-');
const root = mkTmp('msd-2544-plugin-');
t.after(() => cleanup(root));
runInstall(root, 'opencode');
// The adapter is staged as .js, so it needs a marker in its own directory
// now that the config root no longer carries one. A package.json here is
// inert to plugin discovery: OpenCode globs plugins/*.{ts,js}.
assert.ok(fs.existsSync(path.join(root, 'plugins', 'gsd-core.js')));
assert.ok(fs.existsSync(path.join(root, 'plugins', 'msd-core.js')));
const pluginMarker = path.join(root, 'plugins', 'package.json');
assert.ok(fs.existsSync(pluginMarker), 'plugins/package.json marker must be staged');
assert.equal(JSON.parse(fs.readFileSync(pluginMarker, 'utf8')).type, 'commonjs');
});
test('install writes no package.json at the config root when none existed', (t) => {
const root = mkTmp('gsd-2544-noroot-');
const root = mkTmp('msd-2544-noroot-');
t.after(() => cleanup(root));
runInstall(root, 'opencode');
assert.ok(
!fs.existsSync(path.join(root, 'package.json')),
'GSD must not create a package.json in the runtime config root',
'MSD must not create a package.json in the runtime config root',
);
});
test('uninstall removes GSD markers but preserves a user-authored one', (t) => {
const root = mkTmp('gsd-2544-uninstall-');
test('uninstall removes MSD markers but preserves a user-authored one', (t) => {
const root = mkTmp('msd-2544-uninstall-');
t.after(() => cleanup(root));
const userPkg = path.join(root, 'package.json');
fs.writeFileSync(userPkg, USER_PACKAGE_JSON);
@@ -457,7 +457,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
runInstall(root, 'opencode');
runInstall(root, 'opencode', ['--uninstall']);
// AC4 — GSD's own markers are gone; the user's file is untouched.
// AC4 — MSD's own markers are gone; the user's file is untouched.
assert.ok(fs.existsSync(userPkg), 'uninstall must not remove a user-authored package.json');
assert.equal(sha256(fs.readFileSync(userPkg)), before);
assert.ok(
@@ -470,19 +470,19 @@ describe('#2544 regression: install must not clobber the config-root package.jso
);
});
test('uninstall does not prune a plugin dir GSD removed nothing from', (t) => {
const root = mkTmp('gsd-2544-rmdir-');
test('uninstall does not prune a plugin dir MSD removed nothing from', (t) => {
const root = mkTmp('msd-2544-rmdir-');
t.after(() => cleanup(root));
runInstall(root, 'opencode');
// Strip GSD's own artifacts by hand, leaving an EMPTY plugins/ directory
// that — from uninstall's point of view — GSD never filled. Hoisting the
// Strip MSD's own artifacts by hand, leaving an EMPTY plugins/ directory
// that — from uninstall's point of view — MSD never filled. Hoisting the
// rmdir out of the adapter-exists guard (so the marker-only case could
// prune) must not widen it into deleting a user-created empty plugin dir:
// that is the same "don't touch territory GSD didn't fill" principle this
// that is the same "don't touch territory MSD didn't fill" principle this
// issue is about, inverted.
const pluginsDir = path.join(root, 'plugins');
fs.unlinkSync(path.join(pluginsDir, 'gsd-core.js'));
fs.unlinkSync(path.join(pluginsDir, 'msd-core.js'));
fs.unlinkSync(path.join(pluginsDir, 'package.json'));
assert.deepEqual(fs.readdirSync(pluginsDir), [], 'precondition: the dir is empty');
@@ -490,19 +490,19 @@ describe('#2544 regression: install must not clobber the config-root package.jso
assert.ok(
fs.existsSync(pluginsDir),
'an empty plugin dir GSD removed nothing from must survive uninstall',
'an empty plugin dir MSD removed nothing from must survive uninstall',
);
});
test('uninstall reclaims the plugin-dir marker even if the adapter is already gone', (t) => {
const root = mkTmp('gsd-2544-partial-');
const root = mkTmp('msd-2544-partial-');
t.after(() => cleanup(root));
runInstall(root, 'opencode');
// Model a partial install / hand-deleted adapter. The marker cleanup must
// not be gated on the adapter still being present, or it is stranded and
// the directory can never prune.
fs.unlinkSync(path.join(root, 'plugins', 'gsd-core.js'));
fs.unlinkSync(path.join(root, 'plugins', 'msd-core.js'));
runInstall(root, 'opencode', ['--uninstall']);
assert.ok(
@@ -511,12 +511,12 @@ describe('#2544 regression: install must not clobber the config-root package.jso
);
});
test('a pre-existing hooks/ dir GSD never fills stays marker-free', (t) => {
const root = mkTmp('gsd-2544-stagedhooks-');
test('a pre-existing hooks/ dir MSD never fills stays marker-free', (t) => {
const root = mkTmp('msd-2544-stagedhooks-');
t.after(() => cleanup(root));
// Scope, stated precisely: this pins the OUTCOME — a pre-existing,
// GSD-untouched hooks/ stays marker-free — for a runtime GSD stages no .js
// MSD-untouched hooks/ stays marker-free — for a runtime MSD stages no .js
// into. It does NOT exercise installSharedHooksBundle's `stagedHooks` gate:
// zcode declares skipSharedHooksInstall, so the outer guard in bin/install.js
// skips that helper entirely and the gate is never evaluated. For a runtime
@@ -528,10 +528,10 @@ describe('#2544 regression: install must not clobber the config-root package.jso
// ZCode, not Windsurf. Windsurf was the original choice because
// hostBehaviors.skipSharedHooksInstall kept it out of the shared bundle —
// but #2717 then began staging cursor/windsurf/codex .js hooks via dedicated
// paths and writing the marker beside them, so for those three GSD now DOES
// paths and writing the marker beside them, so for those three MSD now DOES
// fill hooks/ and the marker is correct. ZCode is the durable choice: per
// #1821 it has hooksSurface:'none' AND no plugin surface to spawn hooks, so
// GSD stages no .js there by either route. (Measured on this tree: zcode
// MSD stages no .js there by either route. (Measured on this tree: zcode
// stages 0 .js hooks and gets no marker; windsurf stages 2 and gets one.)
const userHooks = path.join(root, 'hooks');
fs.mkdirSync(userHooks, { recursive: true });
@@ -541,7 +541,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
assert.ok(
!fs.existsSync(path.join(userHooks, 'package.json')),
'GSD must not mark a hooks/ directory it never staged into',
'MSD must not mark a hooks/ directory it never staged into',
);
assert.ok(
fs.existsSync(path.join(userHooks, 'my-hook.js')),
@@ -550,7 +550,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('pi: the extensions/ marker is installed and reclaimed on uninstall', (t) => {
const root = mkTmp('gsd-2544-pi-');
const root = mkTmp('msd-2544-pi-');
t.after(() => cleanup(root));
runInstall(root, 'pi');
@@ -563,7 +563,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('pi: a user-authored extensions/package.json survives install and uninstall', (t) => {
const root = mkTmp('gsd-2544-pi-user-');
const root = mkTmp('msd-2544-pi-user-');
t.after(() => cleanup(root));
const extDir = path.join(root, 'extensions');
@@ -582,7 +582,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('kimi: the marker lives under hooks/, and the legacy root marker is retired', (t) => {
const root = mkTmp('gsd-2544-kimi-');
const root = mkTmp('msd-2544-kimi-');
t.after(() => cleanup(root));
// HOME is redirected to `root`, so kimi's native hook root
@@ -612,7 +612,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('kimi: a user-authored package.json at the .kimi root is never retired', (t) => {
const root = mkTmp('gsd-2544-kimi-user-');
const root = mkTmp('msd-2544-kimi-user-');
t.after(() => cleanup(root));
const kimiRoot = path.join(root, '.kimi');
fs.mkdirSync(kimiRoot, { recursive: true });
@@ -627,7 +627,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('kimi: uninstall reclaims the hooks/ marker', (t) => {
const root = mkTmp('gsd-2544-kimi-uninstall-');
const root = mkTmp('msd-2544-kimi-uninstall-');
t.after(() => cleanup(root));
const kimiRoot = path.join(root, '.kimi');
@@ -642,7 +642,7 @@ describe('#2544 regression: install must not clobber the config-root package.jso
});
test('uninstall retires a pre-#2544 config-root marker', (t) => {
const root = mkTmp('gsd-2544-legacy-');
const root = mkTmp('msd-2544-legacy-');
t.after(() => cleanup(root));
runInstall(root, 'opencode');