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

@@ -1,25 +1,25 @@
/**
* Regression (#498, adversarial-review finding): the custom-file backup step in
* update.md must derive RUNTIME_DIR from GSD_DIR.
* update.md must derive RUNTIME_DIR from MSD_DIR.
*
* The get_installed_version step was rewritten to call `gsd-tools update-context`
* and now emits GSD_DIR (the resolved config dir) instead of the old probe-loop
* The get_installed_version step was rewritten to call `msd-tools update-context`
* and now emits MSD_DIR (the resolved config dir) instead of the old probe-loop
* variables LOCAL_DIR / GLOBAL_DIR. The backup_custom_files step still read
* LOCAL_DIR / GLOBAL_DIR, which are no longer assigned anywhere — so RUNTIME_DIR
* went empty for every LOCAL/GLOBAL install and detect-custom-files was skipped.
* Because the update then runs a clean install that wipes managed dirs
* (commands/gsd, gsd-core), user-added files inside those dirs could be
* (commands/msd, msd-core), user-added files inside those dirs could be
* deleted without the intended backup.
*
* This locks the fix: RUNTIME_DIR comes from GSD_DIR, and the dead LOCAL_DIR /
* This locks the fix: RUNTIME_DIR comes from MSD_DIR, and the dead LOCAL_DIR /
* GLOBAL_DIR references are gone.
*
* Source-text-is-the-product: update.md's bash blocks ARE the deployed /gsd:update
* Source-text-is-the-product: update.md's bash blocks ARE the deployed /msd:update
* program; asserting their shape is asserting on the deployed contract.
*/
// allow-test-rule: source-text-is-the-product (#3338)
// update.md's bash blocks ARE the deployed /gsd:update program; asserting
// update.md's bash blocks ARE the deployed /msd:update program; asserting
// their shape is asserting on the deployed contract. The data-loss behavior
// only manifests against a real install during a clean reinstall, which CI
// does not perform.
@@ -30,9 +30,9 @@ const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { extractFencedBlock } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
const { extractFencedBlock } = require('../msd-core/bin/lib/markdown-sectionizer.cjs');
const UPDATE_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'update.md');
const UPDATE_MD = path.join(__dirname, '..', 'msd-core', 'workflows', 'update.md');
function codeOnly(file) {
// Strip fenced-block prose is unnecessary here; we assert on the whole doc
@@ -54,7 +54,7 @@ describe('#4153 regression: unresolved update targets stop before later workflow
assert.ok(unresolved >= 0, 'unresolved target must have a typed result');
assert.match(step, /TARGET_RUNTIME=""/);
assert.match(step, /GSD_DIR=""/);
assert.match(step, /MSD_DIR=""/);
assert.match(
step,
/otherwise leave (?:it )?empty/,
@@ -63,10 +63,10 @@ describe('#4153 regression: unresolved update targets stop before later workflow
assert.match(step, /`\/\.claude\/` -> `claude`/);
assert.match(step, /`\/\.windsurf\/`, `\/\.devin\/` -> `windsurf`/);
assert.doesNotMatch(step, /otherwise `?claude`?\./);
assert.match(step, /INSTALL_SCOPE` is `UNKNOWN`, `TARGET_RUNTIME` is empty, or `GSD_DIR` is empty/);
assert.match(step, /INSTALL_SCOPE` is `UNKNOWN`, `TARGET_RUNTIME` is empty, or `MSD_DIR` is empty/);
assert.match(step, /rerun from a valid installed runtime/i);
assert.match(step, /Rerun from a valid installed runtime: `\/gsd:update`\./);
assert.match(step, /npx -y --package=@opengsd\/gsd-core@latest -- gsd-core --global/);
assert.match(step, /Rerun from a valid installed runtime: `\/msd:update`\./);
assert.match(step, /npx -y --package=@golem15\/msd-core@latest -- msd-core --global/);
assert.match(step, /target runtime \(`claude`, `opencode`, `kilo`, `codex`, `antigravity`, `windsurf`\)/);
assert.ok(exit > unresolved, 'unresolved target must exit before the next step');
@@ -87,9 +87,9 @@ describe('#4153 regression: unresolved update targets stop before later workflow
const mutationSpies = [
{ name: 'version check', text: src, needle: 'check-latest-version.cjs', after: end },
{ name: 'custom-file detection', text: src, needle: 'detect-custom-files --config-dir', after: end },
{ name: 'resolved installer', text: src, needle: 'npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core "$RUNTIME_FLAG"', after: end },
{ name: 'update-cache removal', text: src, needle: 'rm -f "$HOME/.cache/gsd/gsd-update-check"', after: end },
{ name: 'restore apply', text: src, needle: 'restore-custom-files --config-dir "$GSD_DIR" --apply', after: end },
{ name: 'resolved installer', text: src, needle: 'npx -y --package=@golem15/msd-core@"$TAG" -- msd-core "$RUNTIME_FLAG"', after: end },
{ name: 'update-cache removal', text: src, needle: 'rm -f "$HOME/.cache/msd/msd-update-check"', after: end },
{ name: 'restore apply', text: src, needle: 'restore-custom-files --config-dir "$MSD_DIR" --apply', after: end },
{ name: 'patch check', text: src, needle: 'check_local_patches', after: end },
];
for (const { name, text, needle, after } of mutationSpies) {
@@ -126,14 +126,14 @@ describe('#4153 regression: unresolved update targets stop before later workflow
});
});
describe('#498 regression: update.md backup uses GSD_DIR, not the removed LOCAL_DIR/GLOBAL_DIR', () => {
describe('#498 regression: update.md backup uses MSD_DIR, not the removed LOCAL_DIR/GLOBAL_DIR', () => {
const src = codeOnly(UPDATE_MD);
test('RUNTIME_DIR is assigned from GSD_DIR', () => {
test('RUNTIME_DIR is assigned from MSD_DIR', () => {
assert.match(
src,
/RUNTIME_DIR="\$GSD_DIR"/,
'backup_custom_files must set RUNTIME_DIR="$GSD_DIR" (the resolved config dir from update-context)',
/RUNTIME_DIR="\$MSD_DIR"/,
'backup_custom_files must set RUNTIME_DIR="$MSD_DIR" (the resolved config dir from update-context)',
);
});
@@ -166,7 +166,7 @@ describe('#498 regression: update.md backup uses GSD_DIR, not the removed LOCAL_
// Reads product workflow/command markdown to verify the --next RC channel
// contract.
// Issue #815: `/gsd-update --next` (alias `--rc`) must thread the @next dist-tag
// Issue #815: `/msd-update --next` (alias `--rc`) must thread the @next dist-tag
// through the whole update flow (version check + install) while leaving the
// default @latest path unchanged.
@@ -176,8 +176,8 @@ const fs = require('node:fs');
const path = require('node:path');
const ROOT815 = path.join(__dirname, '..');
const WF = fs.readFileSync(path.join(ROOT815, 'gsd-core', 'workflows', 'update.md'), 'utf8');
const CMD = fs.readFileSync(path.join(ROOT815, 'commands', 'gsd', 'update.md'), 'utf8');
const WF = fs.readFileSync(path.join(ROOT815, 'msd-core', 'workflows', 'update.md'), 'utf8');
const CMD = fs.readFileSync(path.join(ROOT815, 'commands', 'msd', 'update.md'), 'utf8');
test('issue #815: workflow parses --next/--rc into a TAG channel', () => {
assert.match(WF, /--next/);
@@ -188,21 +188,21 @@ test('issue #815: workflow parses --next/--rc into a TAG channel', () => {
test('issue #815: version check threads the tag through check-latest-version.cjs', () => {
// The script path is double-quoted in the shell command, so the line is:
// node "$GSD_DIR/gsd-core/bin/check-latest-version.cjs" --json --tag "$TAG"
// node "$MSD_DIR/msd-core/bin/check-latest-version.cjs" --json --tag "$TAG"
// The closing " on the script path sits between .cjs and --json.
assert.match(WF, /check-latest-version\.cjs"? --json --tag "\$TAG"/);
});
test('issue #815: install uses the selected tag, not a hardcoded @latest', () => {
const robust = WF.match(/npx -y --package=@opengsd\/gsd-core@"\$TAG" -- gsd-core/g) || [];
const robust = WF.match(/npx -y --package=@golem15\/msd-core@"\$TAG" -- msd-core/g) || [];
const runUpdateStart = WF.indexOf('<step name="run_update">');
const runUpdateEnd = WF.indexOf('</step>', runUpdateStart);
assert.ok(runUpdateStart >= 0 && runUpdateEnd > runUpdateStart, 'run_update step must exist');
const runUpdate = WF.slice(runUpdateStart, runUpdateEnd);
assert.ok(robust.length >= 2, `expected >=2 tag-parameterized npx invocations, found ${robust.length}`);
assert.doesNotMatch(runUpdate, /--package=@opengsd\/gsd-core@latest -- gsd-core/,
assert.doesNotMatch(runUpdate, /--package=@golem15\/msd-core@latest -- msd-core/,
'install lines must not hardcode @latest once --next exists');
assert.doesNotMatch(runUpdate, /--package=@opengsd\/gsd-core@(?:latest|next|beta|canary|rc) -- gsd-core/,
assert.doesNotMatch(runUpdate, /--package=@golem15\/msd-core@(?:latest|next|beta|canary|rc) -- msd-core/,
'install lines must use the $TAG variable, never a hardcoded dist-tag literal');
});
@@ -242,7 +242,7 @@ const assert2470 = require('node:assert/strict');
const fs2470 = require('fs');
const path2470 = require('path');
const UPDATE_MD_2470 = path2470.join(__dirname, '..', 'gsd-core', 'workflows', 'update.md');
const UPDATE_MD_2470 = path2470.join(__dirname, '..', 'msd-core', 'workflows', 'update.md');
__d2470('update.md — no bare ~.claude path references (#2470)', () => {
const content = fs2470.readFileSync(UPDATE_MD_2470, 'utf-8');
@@ -277,14 +277,14 @@ __d2470('update.md — no bare ~.claude path references (#2470)', () => {
// Regression guard for bug #3130.
//
// Two failure modes were observed with the pre-fix npx invocation form:
// 1. Cache-stale: bare `npx -y @opengsd/gsd-core@<tag>` hits npx's local
// 1. Cache-stale: bare `npx -y @golem15/msd-core@<tag>` hits npx's local
// cache and may pull an older version instead of the target tag.
// 2. Token-routing: Bash-tool wrappers misroute the `@` token in
// `@opengsd/gsd-core@<tag>`, causing npm to error with
// "Unknown command: @opengsd/gsd-core@<tag>".
// `@golem15/msd-core@<tag>`, causing npm to error with
// "Unknown command: @golem15/msd-core@<tag>".
//
// The robust form is:
// npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core $ARGS
// npx -y --package=@golem15/msd-core@"$TAG" -- msd-core $ARGS
//
// `--package=` forces a fresh registry fetch, bypassing the npx cache.
// `--` clearly delineates npx flags from the run-command, preventing
@@ -298,15 +298,15 @@ const fs3130 = require('node:fs');
const path3130 = require('node:path');
const ROOT_3130 = path3130.join(__dirname, '..');
const UPDATE_WF_3130 = path3130.join(ROOT_3130, 'gsd-core', 'workflows', 'update.md');
const UPDATE_WF_3130 = path3130.join(ROOT_3130, 'msd-core', 'workflows', 'update.md');
const src3130 = fs3130.readFileSync(UPDATE_WF_3130, 'utf8');
__t3130('bug #3130: update.md contains no bare npx invocations (cache-stale form)', () => {
// Any occurrence of `npx -y @opengsd/gsd-core@<something>` without `--package=`
// Any occurrence of `npx -y @golem15/msd-core@<something>` without `--package=`
// is the stale form that triggers the two failure modes.
// eslint-disable-next-line local/no-unbounded-quantifier -- parses maintainer-authored update.md workflow, bounded prose, not adversarial input
const stale = (src3130.match(/npx -y @opengsd\/gsd-core@\S+[^\r\n]*/g) || []);
const stale = (src3130.match(/npx -y @golem15\/msd-core@\S+[^\r\n]*/g) || []);
assert3130.deepEqual(
stale,
[],
@@ -318,7 +318,7 @@ __t3130('bug #3130: update.md has exactly two robust resolved-install invocation
const start = src3130.indexOf('<step name="run_update">');
const end = src3130.indexOf('</step>', start);
assert3130.ok(start >= 0 && end > start, 'run_update step must exist');
const robust = (src3130.slice(start, end).match(/npx -y --package=@opengsd\/gsd-core@\S+ -- gsd-core/g) || []);
const robust = (src3130.slice(start, end).match(/npx -y --package=@golem15\/msd-core@\S+ -- msd-core/g) || []);
assert3130.strictEqual(
robust.length,
2,
@@ -340,13 +340,13 @@ __t3130('bug #3130: update.md has exactly two robust resolved-install invocation
const path4153parity = require('node:path');
const fs4153parity = require('node:fs');
const { RUNTIME_DIRS: RUNTIME_DIRS_4153 } = require(
path4153parity.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'update-context.cjs'),
path4153parity.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'update-context.cjs'),
);
const { splitLines: splitLines4153parity } = require('../gsd-core/bin/lib/text-lines.cjs');
const { splitLines: splitLines4153parity } = require('../msd-core/bin/lib/text-lines.cjs');
__t4153parity('update.md PREFERRED_RUNTIME table matches RUNTIME_DIRS', () => {
const src = fs4153parity.readFileSync(
path4153parity.join(__dirname, '..', 'gsd-core', 'workflows', 'update.md'),
path4153parity.join(__dirname, '..', 'msd-core', 'workflows', 'update.md'),
'utf8',
);
const line = splitLines4153parity(src).find((l) => l.includes('Infer `PREFERRED_RUNTIME` from the path'));