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,5 +1,5 @@
/**
* Regression test for #2014: gsd-tools commit --files silently deletes
* Regression test for #2014: msd-tools commit --files silently deletes
* planning files when a filename passed via --files does not exist on disk.
*
* Prior to this fix, when --files STATE.md was passed and STATE.md did not
@@ -15,9 +15,9 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync } = require('child_process');
const { createTempGitProject, cleanup, runGsdTools } = require('./helpers.cjs');
const { createTempGitProject, cleanup, runMsdTools } = require('./helpers.cjs');
const fc = require('fast-check');
const { collectListFlagValues, COMMIT_LIST_FLAGS } = require('../gsd-core/bin/gsd-tools.cjs');
const { collectListFlagValues, COMMIT_LIST_FLAGS } = require('../msd-core/bin/msd-tools.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
// #3145: class-norm timeout, not a per-suite value — see helpers/timeouts.cjs.
const { GIT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
@@ -42,7 +42,7 @@ describe('commit --files: missing files must not stage deletions (#2014)', () =>
test('passing --files for a missing tracked file does not commit a deletion', () => {
// STATE.md is tracked in git but deleted from disk.
// commit --files .planning/STATE.md should skip it (no deletion committed).
runGsdTools(
runMsdTools(
['commit', 'test commit', '--files', '.planning/STATE.md'],
tmpDir
);
@@ -66,7 +66,7 @@ describe('commit --files: missing files must not stage deletions (#2014)', () =>
// Create ROADMAP.md -- this file exists, should be staged normally
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n\nPhase 01.\n');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'add roadmap', '--files', '.planning/ROADMAP.md'],
tmpDir
);
@@ -86,7 +86,7 @@ describe('commit --files: missing files must not stage deletions (#2014)', () =>
// ROADMAP.md exists on disk, STATE.md does not
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
runGsdTools(
runMsdTools(
['commit', 'partial files', '--files', '.planning/ROADMAP.md', '.planning/STATE.md'],
tmpDir
);
@@ -152,7 +152,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
});
test('records the move at file granularity and leaves the peer file alone', () => {
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files', '.planning/todos/completed/mine.md', '.planning/STATE.md',
'--files-removed', '.planning/todos/pending/mine.md'],
@@ -184,7 +184,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
gitOrThrow(['commit', '-m', 'seed a todo that stays'], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS });
fs.appendFileSync(path.join(tmpDir, PENDING, 'stays.md'), 'edited by a peer, uncommitted\n');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files', '.planning/todos/completed/mine.md',
'--files-removed', '.planning/todos/pending/'],
@@ -205,7 +205,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
fs.copyFileSync(path.join(tmpDir, COMPLETED, 'mine.md'), path.join(tmpDir, PENDING, 'mine.md'));
const head = gitOrThrow(['rev-parse', 'HEAD'], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS }).trim();
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): bad declaration',
'--files', '.planning/todos/completed/mine.md',
'--files-removed', '.planning/todos/pending/mine.md'],
@@ -228,7 +228,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
});
test('a --files-removed path git never tracked is a no-op, not an error', () => {
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files', '.planning/todos/completed/mine.md',
'--files-removed', '.planning/todos/pending/mine.md', '.planning/todos/pending/never-tracked.md'],
@@ -242,7 +242,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
});
test('--files-removed alone is a declared scope, not the unscoped .planning/ sweep', () => {
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: drop a todo', '--files-removed', '.planning/todos/pending/mine.md'],
tmpDir,
);
@@ -255,7 +255,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
// A tracked file that is temporarily absent (NOT moved) named via --files
// must still be skipped, even though the same call declares a removal.
fs.unlinkSync(path.join(tmpDir, '.planning', 'STATE.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files', '.planning/todos/completed/mine.md', '.planning/STATE.md',
'--files-removed', '.planning/todos/pending/mine.md'],
@@ -279,7 +279,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
gitOrThrow(['commit', '-m', 'seed a symlink'], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS });
const head = gitOrThrow(['rev-parse', 'HEAD'], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS }).trim();
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files', '.planning/todos/completed/mine.md',
'--files-removed', '.planning/todos/pending/dangling'],
@@ -297,7 +297,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
// `git rm` before the call empties the index entry; `ls-files` alone would
// never list it, so the path would miss the pathspec.
gitOrThrow(['rm', '-q', '--cached', path.join(PENDING, 'mine.md')], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS });
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: drop a todo', '--files-removed', '.planning/todos/pending/mine.md'],
tmpDir,
);
@@ -312,7 +312,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
gitOrThrow(['commit', '-m', 'seed a second todo'], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS });
const head = gitOrThrow(['rev-parse', 'HEAD'], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS }).trim();
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files-removed', '.planning/todos/pending/mine.md', '.planning/todos/pending/stays-put.md'],
tmpDir,
@@ -340,7 +340,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
gitOrThrow(['rm', '-q', cafe], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS }); // caller-staged deletion
fs.copyFileSync(path.join(tmpDir, COMPLETED, 'mine.md'), path.join(tmpDir, PENDING, 'mine.md')); // contradiction
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files-removed', '.planning/todos/pending/café.md', '.planning/todos/pending/mine.md'],
tmpDir,
@@ -364,7 +364,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
gitOrThrow(['add', PENDING], { cwd: fresh, timeoutMs: GIT_TIMEOUT_MS });
fs.unlinkSync(path.join(fresh, PENDING, 'gone.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: root commit',
'--files', '.planning/todos/pending/a.md',
'--files-removed', '.planning/todos/pending/gone.md'],
@@ -382,7 +382,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
// slice-to-end list swept `b` into --files; a list that stops at ANY
// `--` token silently drops it instead (review of #4253). A list runs to
// the next LIST flag and skips boolean flags on the way.
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files', '.planning/todos/completed/mine.md', '--no-verify', '.planning/STATE.md',
'--files-removed', '.planning/todos/pending/mine.md'],
@@ -401,7 +401,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
// a parser that stops at the next list flag — including a repeat of the
// same one — silently dropped b (found by the round's comment audit).
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files', '.planning/todos/completed/mine.md', '--files', '.planning/ROADMAP.md',
'--files-removed', '.planning/todos/pending/mine.md'],
@@ -416,7 +416,7 @@ describe('commit --files-removed: caller-declared deletions record a move (#4208
});
test('--files-removed before --files parses both lists and the message', () => {
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs(phase-5): close 1 resolved todo(s)',
'--files-removed', '.planning/todos/pending/mine.md',
'--files', '.planning/todos/completed/mine.md'],
@@ -499,7 +499,7 @@ describe('commit --files-removed: index states absent by design are never remova
// this repo's own named anti-pattern for I/O-failure injection (see
// CLAUDE.md "Cross-platform test IO-failure injection") -- and it was the
// actual root cause here: the two tests below failed under a real remote
// `gsd-test` run against unmodified `next` (root inside the bench
// `msd-test` run against unmodified `next` (root inside the bench
// container) while passing on an unprivileged workstation, and every OTHER
// fault-injection test in this file that does NOT depend on a permission
// check (the timeout hook two tests down that just sleeps; the mode-flip
@@ -515,7 +515,7 @@ describe('commit --files-removed: index states absent by design are never remova
}
function installCacheinfoRestoreFailureShim() {
const realGit = findRealGit();
const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-fake-git-'));
const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-fake-git-'));
fs.writeFileSync(path.join(shimDir, 'git'), [
'#!/bin/sh',
'has_cacheinfo=0',
@@ -535,7 +535,7 @@ describe('commit --files-removed: index states absent by design are never remova
test('a directory entry leaves a hand-deleted submodule gitlink in the index and records only the file move', () => {
seedMove();
addSubmoduleThenDeleteDir();
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: close a todo', '--files', '.planning/todos/completed/mine.md', '--files-removed', '.planning/todos/pending/'],
tmpDir,
);
@@ -551,7 +551,7 @@ describe('commit --files-removed: index states absent by design are never remova
seedMove();
addSubmoduleThenDeleteDir();
const head = git(['rev-parse', 'HEAD']);
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration', '--files', '.planning/todos/completed/mine.md', '--files-removed', '.planning/todos/pending/sub'],
tmpDir,
);
@@ -575,7 +575,7 @@ describe('commit --files-removed: index states absent by design are never remova
fs.unlinkSync(path.join(tmpDir, PENDING, 'sparse.md'));
assert.strictEqual(porcelain(path.join(PENDING, 'sparse.md')), '', 'git itself does not report a skip-worktree path as deleted');
const dirForm = runGsdTools(
const dirForm = runMsdTools(
['commit', 'docs: close a todo', '--files', '.planning/todos/completed/mine.md', '--files-removed', '.planning/todos/pending/'],
tmpDir,
);
@@ -584,7 +584,7 @@ describe('commit --files-removed: index states absent by design are never remova
assert.match(git(['ls-files', '-v', '--', PENDING]), /^S \.planning\/todos\/pending\/sparse\.md$/m, 'the sparse entry stays in the index, still skip-worktree');
const head = git(['rev-parse', 'HEAD']);
const named = runGsdTools(['commit', 'docs: bad declaration', '--files-removed', '.planning/todos/pending/sparse.md'], tmpDir);
const named = runMsdTools(['commit', 'docs: bad declaration', '--files-removed', '.planning/todos/pending/sparse.md'], tmpDir);
const parsed = JSON.parse(named.output);
assert.strictEqual(parsed.reason, 'staging_failed', named.output);
assert.strictEqual(parsed.file, '.planning/todos/pending/sparse.md');
@@ -601,7 +601,7 @@ describe('commit --files-removed: index states absent by design are never remova
seedMove();
addSubmoduleThenDeleteDir();
const head = git(['rev-parse', 'HEAD']);
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration', '--files', '.planning/todos/completed/mine.md', '--files-removed', path.join(tmpDir, PENDING, 'sub')],
tmpDir,
);
@@ -617,7 +617,7 @@ describe('commit --files-removed: index states absent by design are never remova
const alias = tmpDir + '-alias';
fs.symlinkSync(tmpDir, alias, 'dir');
try {
const viaLink = runGsdTools(
const viaLink = runMsdTools(
['commit', 'docs: bad declaration', '--files', '.planning/todos/completed/mine.md', '--files-removed', path.join(alias, PENDING, 'sub')],
tmpDir,
);
@@ -641,7 +641,7 @@ describe('commit --files-removed: index states absent by design are never remova
const before = git(['ls-files', '-s', '--', path.join(PENDING, 'planned.md')]);
assert.match(before, /^100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0/, 'fixture: intent-to-add entry present');
const dirForm = runGsdTools(
const dirForm = runMsdTools(
['commit', 'docs: close a todo', '--files', '.planning/todos/completed/mine.md', '--files-removed', '.planning/todos/pending/'],
tmpDir,
);
@@ -649,7 +649,7 @@ describe('commit --files-removed: index states absent by design are never remova
assert.deepStrictEqual(nameStatus(), ['A\t.planning/todos/completed/mine.md', 'D\t.planning/todos/pending/mine.md']);
assert.strictEqual(git(['ls-files', '-s', '--', path.join(PENDING, 'planned.md')]), before, 'the intent-to-add entry is left alone');
const named = runGsdTools(['commit', 'docs: bad declaration', '--files-removed', '.planning/todos/pending/planned.md'], tmpDir);
const named = runMsdTools(['commit', 'docs: bad declaration', '--files-removed', '.planning/todos/pending/planned.md'], tmpDir);
const parsed = JSON.parse(named.output);
assert.strictEqual(parsed.reason, 'staging_failed', named.output);
assert.match(parsed.error, /intent-to-add/);
@@ -659,7 +659,7 @@ describe('commit --files-removed: index states absent by design are never remova
test('an assume-unchanged path named directly fails closed and stays in the index', () => {
seedMove();
git(['update-index', '--assume-unchanged', '--', '.planning/todos/pending/mine.md']);
const result = runGsdTools(['commit', 'docs: drop a todo', '--files-removed', '.planning/todos/pending/mine.md'], tmpDir);
const result = runMsdTools(['commit', 'docs: drop a todo', '--files-removed', '.planning/todos/pending/mine.md'], tmpDir);
const parsed = JSON.parse(result.output);
assert.strictEqual(parsed.reason, 'staging_failed', result.output);
assert.match(parsed.error, /assume-unchanged/);
@@ -680,7 +680,7 @@ describe('commit --files-removed: index states absent by design are never remova
const before = git(['ls-files', '-s', '--', PENDING], fresh);
fs.unlinkSync(path.join(fresh, PENDING, 'gone.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: root commit', '--files-removed', '.planning/todos/pending/gone.md', '.planning/todos/pending/stays.md'],
fresh,
);
@@ -708,7 +708,7 @@ describe('commit --files-removed: index states absent by design are never remova
const before = git(['ls-files', '-s', '--', PENDING], fresh);
fs.unlinkSync(path.join(fresh, PENDING, 'gone.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: root commit', '--files-removed', '.planning/todos/pending/gone.md'],
fresh,
);
@@ -737,7 +737,7 @@ describe('commit --files-removed: index states absent by design are never remova
const head = git(['rev-parse', 'HEAD']);
fs.unlinkSync(path.join(tmpDir, PENDING, 'gone.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: remove an uncommitted path', '--files-removed', '.planning/todos/pending/gone.md'],
tmpDir,
);
@@ -769,7 +769,7 @@ describe('commit --files-removed: index states absent by design are never remova
const shim = installCacheinfoRestoreFailureShim();
t.after(() => cleanup(shim.shimDir));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: remove an uncommitted path', '--files-removed', '.planning/todos/pending/gone.md'],
tmpDir,
{ PATH: shim.path },
@@ -800,7 +800,7 @@ describe('commit --files-removed: index states absent by design are never remova
// mine.md was moved away (a real removal); stays.md is still on disk, so
// declaring it removed contradicts the declaration and fails the call.
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files-removed', '.planning/todos/pending/mine.md', '.planning/todos/pending/stays.md'],
tmpDir,
@@ -833,7 +833,7 @@ describe('commit --files-removed: index states absent by design are never remova
const before = git(['ls-files', '-s', '--', PENDING]);
fs.writeFileSync(path.join(tmpDir, '.git', 'index.lock'), '');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files-removed', '.planning/todos/pending/mine.md', '.planning/todos/pending/stays.md'],
tmpDir,
@@ -871,7 +871,7 @@ describe('commit --files-removed: index states absent by design are never remova
const emptyConfig = path.join(tmpDir, 'empty.gitconfig');
fs.writeFileSync(emptyConfig, '');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: close a todo', '--files-removed', '.planning/todos/pending/mine.md'],
tmpDir,
{ GIT_CONFIG_GLOBAL: emptyConfig, GIT_CONFIG_NOSYSTEM: '1' },
@@ -904,7 +904,7 @@ describe('commit --files-removed: index states absent by design are never remova
const before = gitOrThrow(['ls-files', '-s', '-z', '--', PENDING], { cwd: tmpDir, timeoutMs: GIT_TIMEOUT_MS });
fs.unlinkSync(path.join(tmpDir, PENDING, 'café.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: remove an uncommitted path', '--files-removed', '.planning/todos/pending/café.md'],
tmpDir,
);
@@ -944,7 +944,7 @@ describe('commit --files-removed: index states absent by design are never remova
const emptyConfig = path.join(tmpDir, 'empty.gitconfig');
fs.writeFileSync(emptyConfig, '');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: remove an uncommitted path', '--files-removed', '.planning/todos/pending/gone.md'],
tmpDir,
{ GIT_CONFIG_GLOBAL: emptyConfig, GIT_CONFIG_NOSYSTEM: '1' },
@@ -975,7 +975,7 @@ describe('commit --files-removed: index states absent by design are never remova
// stays.md is still present, so the call fails and rolls back. Whatever the
// rollback restores, the peers must never have been touched at all.
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files-removed', '.planning/todos/pending/', '.planning/todos/pending/stays.md'],
tmpDir,
@@ -1010,7 +1010,7 @@ describe('commit --files-removed: index states absent by design are never remova
git(['commit', '-q', '-m', 'seed a peer']);
fs.writeFileSync(path.join(tmpDir, 'peer.md'), 'peer, modified\n');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: close a todo', '--files-removed', odd],
tmpDir,
);
@@ -1043,7 +1043,7 @@ describe('commit --files-removed: index states absent by design are never remova
fs.unlinkSync(path.join(tmpDir, PENDING, '*.md'));
fs.writeFileSync(path.join(tmpDir, PENDING, 'peer.md'), 'peer, modified\n');
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: close a todo', '--files-removed', '.planning/todos/pending/'],
tmpDir,
);
@@ -1081,7 +1081,7 @@ describe('commit --files-removed: index states absent by design are never remova
);
// A directory entry: the intent-to-add path must be SKIPPED, not removed.
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: close a todo', '--files-removed', '.planning/todos/pending/'],
tmpDir,
);
@@ -1123,7 +1123,7 @@ describe('commit --files-removed: index states absent by design are never remova
// stays.md is present, so the declaration is contradictory and the call
// rolls back. The rollback must not touch what the caller staged.
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration',
'--files-removed', '.planning/todos/pending/', '.planning/todos/pending/stays.md'],
proj,
@@ -1165,7 +1165,7 @@ describe('commit --files-removed: index states absent by design are never remova
fs.writeFileSync(emptyConfig, '');
const head = git(['rev-parse', 'HEAD']);
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: close a todo', '--files', '.planning/todos/completed/mine.md', '--files-removed', '.planning/todos/pending/mine.md'],
tmpDir,
{ GIT_CONFIG_GLOBAL: emptyConfig, GIT_CONFIG_NOSYSTEM: '1' },
@@ -1196,7 +1196,7 @@ describe('commit --files-removed: index states absent by design are never remova
assert.notEqual(staged, git(['ls-tree', 'HEAD', '--', path.join(PENDING, 'mine.md')]).replace(/\t/, ' '), 'fixture: the staged blob must differ from HEAD');
fs.unlinkSync(path.join(tmpDir, PENDING, 'mine.md'));
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: bad declaration', '--files-removed', '.planning/todos/pending/mine.md', '.planning/todos/pending/present.md'],
tmpDir,
);
@@ -1225,7 +1225,7 @@ describe('commit --files-removed: index states absent by design are never remova
assert.strictEqual(git(['ls-files', '--', path.join(PENDING, 'link') + '/']), '', 'git does not traverse the symlink');
const head = git(['rev-parse', 'HEAD']);
const result = runGsdTools(
const result = runMsdTools(
['commit', 'docs: remove a symlinked dir', '--files-removed', '.planning/todos/pending/link'],
tmpDir,
);