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

@@ -5,17 +5,17 @@
*
* Coverage gaps closed here (tests/install-minimal-hooks.test.cjs already
* covers the installed-tree rows — pi local+global: no `hooks/`, bundle at
* `gsd-hooks/`, manifest keys — and is not duplicated):
* `msd-hooks/`, manifest keys — and is not duplicated):
*
* GROUP A bin/install.js `resolveSharedHooksDirName(runtime)` — the
* descriptor-driven sanitizer that rejects anything that is not a
* plain, non-empty, separator-free, non-dot, non-absolute,
* NUL-free single path segment.
* GROUP B pi/gsd.cjs `_internals.resolveSharedHooksDir(engineRoot)` — the
* GROUP B pi/msd.cjs `_internals.resolveSharedHooksDir(engineRoot)` — the
* adapter-side probe over `SHARED_HOOKS_DIR_CANDIDATES`.
* GROUP C the two latent bundle-directory-NAME dependencies:
* hooks/gsd-check-update-worker.js (stale-hook scan) and
* hooks/gsd-read-injection-scanner.js (own-bundle exclusion).
* hooks/msd-check-update-worker.js (stale-hook scan) and
* hooks/msd-read-injection-scanner.js (own-bundle exclusion).
*
* GROUP A malformed-value cases (empty/whitespace/non-string/traversal/NUL):
* `resolveSharedHooksDirName` sources its raw descriptor value from the
@@ -31,7 +31,7 @@
* GROUP A's real-registry assertions above it.
*/
process.env.GSD_TEST_MODE = process.env.GSD_TEST_MODE || '1';
process.env.MSD_TEST_MODE = process.env.MSD_TEST_MODE || '1';
const { test, describe, before, after } = require('node:test');
const assert = require('node:assert/strict');
@@ -55,7 +55,7 @@ const PROPERTY_DRIVER_TIMEOUT_MS = 60000;
// Requiring the installer (not as main) never runs the CLI — matches the
// existing tests/claude-imperative-reference.test.cjs convention.
const installMod = require('../bin/install.js');
const piExtension = require('../pi/gsd.cjs');
const piExtension = require('../pi/msd.cjs');
const { resolveSharedHooksDir, SHARED_HOOKS_DIR_CANDIDATES } = piExtension._internals;
// ---------------------------------------------------------------------------
@@ -73,8 +73,8 @@ describe('GROUP A.1: resolveSharedHooksDirName — real registry, real runtimes'
}
});
test('the real pi descriptor resolves to "gsd-hooks" (#3023)', () => {
assert.equal(installMod.resolveSharedHooksDirName('pi'), 'gsd-hooks');
test('the real pi descriptor resolves to "msd-hooks" (#3023)', () => {
assert.equal(installMod.resolveSharedHooksDirName('pi'), 'msd-hooks');
});
test('an unknown runtime id, and an empty-string runtime id, both degrade to the default and never throw', () => {
@@ -128,10 +128,10 @@ const HOSTILE_CASES = [
// Trailing dot — Windows silently strips this at creation time, splitting
// the created dir name from the probed-for name. (A trailing ASCII SPACE is
// not exercised here: `raw.trim()` at the top of the function already
// strips it before any guard runs, so 'gsd-hooks ' correctly normalizes to
// the intended 'gsd-hooks' — see the ACCEPTED_CASES entry below, which
// strips it before any guard runs, so 'msd-hooks ' correctly normalizes to
// the intended 'msd-hooks' — see the ACCEPTED_CASES entry below, which
// pins down that verified, non-regressive behavior instead.)
{ label: 'trailing dot', raw: 'gsd-hooks.' },
{ label: 'trailing dot', raw: 'msd-hooks.' },
{ label: 'single-char trailing dot', raw: 'a.' },
// Windows reserved device names — cannot exist as directories on Windows.
{ label: 'reserved CON uppercase', raw: 'CON' },
@@ -146,8 +146,8 @@ const HOSTILE_CASES = [
// default). An over-broad guard would silently retarget a legitimate
// descriptor, which is worse than under-rejecting a hostile one.
const ACCEPTED_CASES = [
{ label: 'ordinary name', raw: 'gsd-hooks', expect: 'gsd-hooks' },
{ label: 'leading-dot hidden dir', raw: '.gsd-hooks', expect: '.gsd-hooks' },
{ label: 'ordinary name', raw: 'msd-hooks', expect: 'msd-hooks' },
{ label: 'leading-dot hidden dir', raw: '.msd-hooks', expect: '.msd-hooks' },
{ label: 'plain word', raw: 'hooks2', expect: 'hooks2' },
{ label: 'CONSOLE (not a reserved device)', raw: 'CONSOLE', expect: 'CONSOLE' },
{ label: 'COM10 (not a reserved device)', raw: 'COM10', expect: 'COM10' },
@@ -157,7 +157,7 @@ const ACCEPTED_CASES = [
// any guard runs, so the descriptor's clearly-intended name survives
// unharmed — rejecting this to the default would be the actual regression
// (see the comment on HOSTILE_CASES above).
{ label: 'trailing space (normalized by existing trim)', raw: 'gsd-hooks ', expect: 'gsd-hooks' },
{ label: 'trailing space (normalized by existing trim)', raw: 'msd-hooks ', expect: 'msd-hooks' },
];
const ALL_SINGLE_CASES = [...MALFORMED_CASES, ...HOSTILE_CASES, ...ACCEPTED_CASES];
@@ -276,7 +276,7 @@ describe('GROUP A.2: resolveSharedHooksDirName — malformed/hostile values + pr
const driverPath = path.join(driverDir, 'driver.cjs');
fs.writeFileSync(driverPath, buildDriverSource());
const registryPath = path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib', 'capability-registry.cjs');
const registryPath = path.join(REPO_ROOT, 'msd-core', 'bin', 'lib', 'capability-registry.cjs');
const installPath = path.join(REPO_ROOT, 'bin', 'install.js');
const fastcheckPath = require.resolve('fast-check');
@@ -359,28 +359,28 @@ describe('GROUP A.2: resolveSharedHooksDirName — malformed/hostile values + pr
});
// ---------------------------------------------------------------------------
// GROUP B — pi/gsd.cjs _internals.resolveSharedHooksDir(engineRoot)
// GROUP B — pi/msd.cjs _internals.resolveSharedHooksDir(engineRoot)
// ---------------------------------------------------------------------------
describe('GROUP B: pi adapter resolveSharedHooksDir (pi/gsd.cjs)', () => {
describe('GROUP B: pi adapter resolveSharedHooksDir (pi/msd.cjs)', () => {
// A real staged bundle always has at least one hook file in it; these tests
// populate every "should qualify" candidate with a placeholder file so they
// exercise the same non-empty invariant defect 2's fix enforces, rather than
// relying on an literally-empty directory that no real install ever produces.
function populate(dirPath) {
fs.mkdirSync(dirPath, { recursive: true });
fs.writeFileSync(path.join(dirPath, 'gsd-placeholder-hook.js'), '// placeholder\n');
fs.writeFileSync(path.join(dirPath, 'msd-placeholder-hook.js'), '// placeholder\n');
}
test('candidate order is exactly ["gsd-hooks", "hooks"] (a reordering that silently prefers the stale dir must fail loudly)', () => {
assert.deepEqual(SHARED_HOOKS_DIR_CANDIDATES, ['gsd-hooks', 'hooks']);
test('candidate order is exactly ["msd-hooks", "hooks"] (a reordering that silently prefers the stale dir must fail loudly)', () => {
assert.deepEqual(SHARED_HOOKS_DIR_CANDIDATES, ['msd-hooks', 'hooks']);
});
test('only gsd-hooks/ exists (populated) -> returns that path', (t) => {
test('only msd-hooks/ exists (populated) -> returns that path', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
populate(path.join(root, 'gsd-hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'gsd-hooks'));
populate(path.join(root, 'msd-hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'msd-hooks'));
});
test('only hooks/ exists (dev-tree back-compat, populated) -> returns that path', (t) => {
@@ -390,12 +390,12 @@ describe('GROUP B: pi adapter resolveSharedHooksDir (pi/gsd.cjs)', () => {
assert.equal(resolveSharedHooksDir(root), path.join(root, 'hooks'));
});
test('both exist and both populated (half-upgraded tree) -> gsd-hooks wins deterministically', (t) => {
test('both exist and both populated (half-upgraded tree) -> msd-hooks wins deterministically', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
populate(path.join(root, 'gsd-hooks'));
populate(path.join(root, 'msd-hooks'));
populate(path.join(root, 'hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'gsd-hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'msd-hooks'));
});
test('neither exists -> null, does not throw', (t) => {
@@ -405,60 +405,60 @@ describe('GROUP B: pi adapter resolveSharedHooksDir (pi/gsd.cjs)', () => {
assert.equal(resolveSharedHooksDir(root), null);
});
test('gsd-hooks exists as a FILE, not a directory -> skipped; falls through to a populated hooks/ when present', (t) => {
test('msd-hooks exists as a FILE, not a directory -> skipped; falls through to a populated hooks/ when present', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
fs.writeFileSync(path.join(root, 'gsd-hooks'), 'not a directory');
fs.writeFileSync(path.join(root, 'msd-hooks'), 'not a directory');
populate(path.join(root, 'hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'hooks'));
});
test('gsd-hooks exists as a FILE and hooks/ is absent -> null', (t) => {
test('msd-hooks exists as a FILE and hooks/ is absent -> null', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
fs.writeFileSync(path.join(root, 'gsd-hooks'), 'not a directory');
fs.writeFileSync(path.join(root, 'msd-hooks'), 'not a directory');
assert.equal(resolveSharedHooksDir(root), null);
});
// ── Defect 2 (adversarial review): empty-bundle qualification ────────────
// An install interrupted between mkdirSync(gsd-hooks) and the file copy
// leaves a directory that EXISTS but is EMPTY. Since gsd-hooks is probed
// FIRST, an empty gsd-hooks/ must lose to a fully-staged legacy hooks/ —
// An install interrupted between mkdirSync(msd-hooks) and the file copy
// leaves a directory that EXISTS but is EMPTY. Since msd-hooks is probed
// FIRST, an empty msd-hooks/ must lose to a fully-staged legacy hooks/ —
// otherwise every hook silently no-ops (runHook's fs.existsSync guard
// degrades per-file, producing no error at all).
test('regression: gsd-hooks/ exists but is EMPTY, hooks/ is populated -> resolves hooks/ (fails before the fix)', (t) => {
test('regression: msd-hooks/ exists but is EMPTY, hooks/ is populated -> resolves hooks/ (fails before the fix)', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
fs.mkdirSync(path.join(root, 'gsd-hooks'), { recursive: true }); // empty — no files written
fs.mkdirSync(path.join(root, 'msd-hooks'), { recursive: true }); // empty — no files written
populate(path.join(root, 'hooks'));
assert.equal(
resolveSharedHooksDir(root),
path.join(root, 'hooks'),
'an empty gsd-hooks/ must not win over a fully-staged legacy hooks/',
'an empty msd-hooks/ must not win over a fully-staged legacy hooks/',
);
});
test('gsd-hooks/ populated, hooks/ populated -> resolves gsd-hooks/', (t) => {
test('msd-hooks/ populated, hooks/ populated -> resolves msd-hooks/', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
populate(path.join(root, 'gsd-hooks'));
populate(path.join(root, 'msd-hooks'));
populate(path.join(root, 'hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'gsd-hooks'));
assert.equal(resolveSharedHooksDir(root), path.join(root, 'msd-hooks'));
});
test('both gsd-hooks/ and hooks/ exist but are BOTH empty -> null', (t) => {
test('both msd-hooks/ and hooks/ exist but are BOTH empty -> null', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
fs.mkdirSync(path.join(root, 'gsd-hooks'), { recursive: true });
fs.mkdirSync(path.join(root, 'msd-hooks'), { recursive: true });
fs.mkdirSync(path.join(root, 'hooks'), { recursive: true });
assert.equal(resolveSharedHooksDir(root), null);
});
test('gsd-hooks/ is empty, hooks/ does not exist -> null', (t) => {
test('msd-hooks/ is empty, hooks/ does not exist -> null', (t) => {
const root = createTempDir('fix-3023-adapter-');
t.after(() => cleanup(root));
fs.mkdirSync(path.join(root, 'gsd-hooks'), { recursive: true });
fs.mkdirSync(path.join(root, 'msd-hooks'), { recursive: true });
assert.equal(resolveSharedHooksDir(root), null);
});
});
@@ -468,25 +468,25 @@ describe('GROUP B: pi adapter resolveSharedHooksDir (pi/gsd.cjs)', () => {
// ---------------------------------------------------------------------------
//
// bin/install.js derives the shared-hooks directory NAME for a runtime from
// the capability registry (resolveSharedHooksDirName). pi/gsd.cjs cannot read
// the capability registry (resolveSharedHooksDirName). pi/msd.cjs cannot read
// that registry at runtime — it must resolve correctly in a dev checkout AND
// in a half-upgraded tree, where the registry's CURRENT answer would be the
// wrong one to probe for (see the SHARED_HOOKS_DIR_CANDIDATES doc comment in
// pi/gsd.cjs) — so it keeps its own hardcoded probe list instead. That is two
// pi/msd.cjs) — so it keeps its own hardcoded probe list instead. That is two
// independent sources of truth for the same name: exactly the "Generative Fix
// Divergence" anti-pattern (CLAUDE.md -> KNOWN DEFECTS: "When sharing
// constants/arrays/parsers between parallel surfaces, add a parity assertion
// test that fails if they diverge."). If a future descriptor rename is not
// mirrored into pi/gsd.cjs, this is the guard that fails loudly instead of
// mirrored into pi/msd.cjs, this is the guard that fails loudly instead of
// every pi hook going quiet with no error.
describe('GROUP D: parity — installer descriptor vs pi/gsd.cjs SHARED_HOOKS_DIR_CANDIDATES', () => {
const REMEDY = 'if the descriptor changes, update SHARED_HOOKS_DIR_CANDIDATES in pi/gsd.cjs to match';
describe('GROUP D: parity — installer descriptor vs pi/msd.cjs SHARED_HOOKS_DIR_CANDIDATES', () => {
const REMEDY = 'if the descriptor changes, update SHARED_HOOKS_DIR_CANDIDATES in pi/msd.cjs to match';
test('the installer-resolved pi descriptor name is a member of the adapter probe list', () => {
const descriptorName = installMod.resolveSharedHooksDirName('pi');
assert.ok(
SHARED_HOOKS_DIR_CANDIDATES.includes(descriptorName),
`pi's capability descriptor resolves to "${descriptorName}", which is NOT in pi/gsd.cjs's ` +
`pi's capability descriptor resolves to "${descriptorName}", which is NOT in pi/msd.cjs's ` +
`SHARED_HOOKS_DIR_CANDIDATES (${JSON.stringify(SHARED_HOOKS_DIR_CANDIDATES)}) — ${REMEDY}.`,
);
});
@@ -496,7 +496,7 @@ describe('GROUP D: parity — installer descriptor vs pi/gsd.cjs SHARED_HOOKS_DI
assert.equal(
SHARED_HOOKS_DIR_CANDIDATES[0],
descriptorName,
`pi/gsd.cjs's SHARED_HOOKS_DIR_CANDIDATES probes "${SHARED_HOOKS_DIR_CANDIDATES[0]}" first, but the ` +
`pi/msd.cjs's SHARED_HOOKS_DIR_CANDIDATES probes "${SHARED_HOOKS_DIR_CANDIDATES[0]}" first, but the ` +
`installer's current descriptor for pi resolves to "${descriptorName}" — a half-upgraded tree (both dirs ` +
`present) would bind to the stale bundle first. ${REMEDY}, with the descriptor's current name listed first.`,
);
@@ -505,7 +505,7 @@ describe('GROUP D: parity — installer descriptor vs pi/gsd.cjs SHARED_HOOKS_DI
test('the back-compat default ("hooks") is present in the adapter probe list', () => {
assert.ok(
SHARED_HOOKS_DIR_CANDIDATES.includes(installMod.SHARED_HOOKS_DIR_DEFAULT),
`pi/gsd.cjs's SHARED_HOOKS_DIR_CANDIDATES (${JSON.stringify(SHARED_HOOKS_DIR_CANDIDATES)}) no longer ` +
`pi/msd.cjs's SHARED_HOOKS_DIR_CANDIDATES (${JSON.stringify(SHARED_HOOKS_DIR_CANDIDATES)}) no longer ` +
`contains the installer's back-compat default ("${installMod.SHARED_HOOKS_DIR_DEFAULT}") — dropping it ` +
`breaks the dev-checkout/back-compat path (a checkout with only a legacy hooks/ dir would resolve to null). ${REMEDY}.`,
);
@@ -520,33 +520,33 @@ const { MANAGED_HOOKS } = require('../hooks/managed-hooks-registry.cjs');
const FIXTURE_HOOK_NAME = MANAGED_HOOKS.find((f) => f.endsWith('.js'));
describe('GROUP C: bundle-directory-name-agnostic hook scripts', () => {
test('gsd-check-update-worker.js detects a stale hook when staged under a non-"hooks"-named bundle directory', (t) => {
test('msd-check-update-worker.js detects a stale hook when staged under a non-"hooks"-named bundle directory', (t) => {
assert.ok(FIXTURE_HOOK_NAME, 'expected at least one .js entry in MANAGED_HOOKS');
const tmpRoot = createTempDir('fix-3023-worker-');
t.after(() => cleanup(tmpRoot));
const bundleDir = path.join(tmpRoot, 'gsd-hooks');
const bundleDir = path.join(tmpRoot, 'msd-hooks');
fs.mkdirSync(bundleDir, { recursive: true });
fs.copyFileSync(
path.join(REPO_ROOT, 'hooks', 'gsd-check-update-worker.js'),
path.join(bundleDir, 'gsd-check-update-worker.js'),
path.join(REPO_ROOT, 'hooks', 'msd-check-update-worker.js'),
path.join(bundleDir, 'msd-check-update-worker.js'),
);
fs.copyFileSync(
path.join(REPO_ROOT, 'hooks', 'managed-hooks-registry.cjs'),
path.join(bundleDir, 'managed-hooks-registry.cjs'),
);
// The worker's own require()s of ../gsd-core/... are relative to
// __dirname (wherever it is physically staged), so a real gsd-core tree
// The worker's own require()s of ../msd-core/... are relative to
// __dirname (wherever it is physically staged), so a real msd-core tree
// must exist one level up from the bundle directory, exactly like the
// real install layout (<configDir>/gsd-hooks + <configDir>/gsd-core would
// real install layout (<configDir>/msd-hooks + <configDir>/msd-core would
// NOT match — but this worker's actual production layout is the shared
// engine tree, one level above the bundle, which this symlink mirrors).
fs.symlinkSync(path.join(REPO_ROOT, 'gsd-core'), path.join(tmpRoot, 'gsd-core'), 'dir');
fs.symlinkSync(path.join(REPO_ROOT, 'msd-core'), path.join(tmpRoot, 'msd-core'), 'dir');
const fixtureHookLines = [
'// gsd-hook-version: 1.0.0',
'// msd-hook-version: 1.0.0',
'// fixture managed hook staged for the #3023 bundle-name-agnostic staleness test',
'module.exports = {};',
'',
@@ -561,7 +561,7 @@ describe('GROUP C: bundle-directory-name-agnostic hook scripts', () => {
const cacheFile = path.join(tmpRoot, 'cache.json');
const result = runNode(
[path.join(bundleDir, 'gsd-check-update-worker.js')],
[path.join(bundleDir, 'msd-check-update-worker.js')],
{
cwd: tmpRoot,
// PATH is cleared so the worker's own npm-registry lookup
@@ -571,9 +571,9 @@ describe('GROUP C: bundle-directory-name-agnostic hook scripts', () => {
env: {
...process.env,
PATH: '',
GSD_PROJECT_VERSION_FILE: versionFile,
GSD_GLOBAL_VERSION_FILE: '',
GSD_CACHE_FILE: cacheFile,
MSD_PROJECT_VERSION_FILE: versionFile,
MSD_GLOBAL_VERSION_FILE: '',
MSD_CACHE_FILE: cacheFile,
},
timeoutMs: STAGED_HOOK_SCRIPT_TIMEOUT_MS,
},
@@ -590,14 +590,14 @@ describe('GROUP C: bundle-directory-name-agnostic hook scripts', () => {
assert.equal(staleEntry.installedVersion, '2.0.0');
});
test('gsd-read-injection-scanner.js excludes a path inside its own (non-"hooks"-named) bundle directory', (t) => {
test('msd-read-injection-scanner.js excludes a path inside its own (non-"hooks"-named) bundle directory', (t) => {
const tmpRoot = createTempDir('fix-3023-scanner-');
t.after(() => cleanup(tmpRoot));
const bundleDir = path.join(tmpRoot, 'gsd-hooks');
const bundleDir = path.join(tmpRoot, 'msd-hooks');
// Stage via copyScriptWithDeps (walks the real require graph — see its doc
// comment / CLAUDE.md "no new copyFileSync line") into a throwaway staging
// root, then relocate the staged hooks/ subtree onto `gsd-hooks` — a
// root, then relocate the staged hooks/ subtree onto `msd-hooks` — a
// NON-"hooks"-named directory is exactly the condition this test exists to
// exercise (#3023), so the staged tree cannot simply be used at its
// repo-relative `hooks/` location. A hand-copied dependency list is what
@@ -607,9 +607,9 @@ describe('GROUP C: bundle-directory-name-agnostic hook scripts', () => {
// to the scanner (or any of its deps) cannot be silently omitted here again.
const stageRoot = createTempDir('fix-3023-scanner-stage-');
t.after(() => cleanup(stageRoot));
copyScriptWithDeps(REPO_ROOT, stageRoot, 'hooks/gsd-read-injection-scanner.js');
copyScriptWithDeps(REPO_ROOT, stageRoot, 'hooks/msd-read-injection-scanner.js');
fs.cpSync(path.join(stageRoot, 'hooks'), bundleDir, { recursive: true });
const scannerPath = path.join(bundleDir, 'gsd-read-injection-scanner.js');
const scannerPath = path.join(bundleDir, 'msd-read-injection-scanner.js');
// Node canonicalizes a module's __dirname via the REAL (symlink-resolved)
// path, so a payload path must be built from the same realpath — on macOS