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:
@@ -3,10 +3,10 @@
|
||||
/**
|
||||
* tests/shadow-report.test.cjs — pure IR unit suite for
|
||||
* `install-shadow-report.cts`'s `buildShadowReport` (#2873, epic #2866 Phase
|
||||
* 4a — governed by `.gsd/phase/feat-2873-cross-scope-shadowing/40-design.md`).
|
||||
* 4a — governed by `.msd/phase/feat-2873-cross-scope-shadowing/40-design.md`).
|
||||
*
|
||||
* Implements matrix section A (`buildShadowReport()`, rows A1-A24) and
|
||||
* properties F1/F4 from `.gsd/phase/feat-2873-cross-scope-shadowing/50-test-matrix.md`.
|
||||
* properties F1/F4 from `.msd/phase/feat-2873-cross-scope-shadowing/50-test-matrix.md`.
|
||||
* The matrix's own "Suites" section names this file `install-shadow-report
|
||||
* .test.cjs`; it is shipped as `shadow-report.test.cjs` instead so its
|
||||
* `lint-test-file-count.cjs` prefix is `shadow` (0 files before this PR, at
|
||||
@@ -43,10 +43,10 @@ const {
|
||||
renderShadowReport,
|
||||
sanitizeForRender,
|
||||
SHADOW_REASON,
|
||||
} = require('../gsd-core/bin/lib/install-shadow-report.cjs');
|
||||
const { resolveScope } = require('../gsd-core/bin/lib/install-scope.cjs');
|
||||
const { MANIFEST_NAME } = require('../gsd-core/bin/lib/installer-migrations.cjs');
|
||||
const { resolveSpecRootReference } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
} = require('../msd-core/bin/lib/install-shadow-report.cjs');
|
||||
const { resolveScope } = require('../msd-core/bin/lib/install-scope.cjs');
|
||||
const { MANIFEST_NAME } = require('../msd-core/bin/lib/installer-migrations.cjs');
|
||||
const { resolveSpecRootReference } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
// ─── Fixture helpers (mirrors tests/installed-surface-resolver.test.cjs) ───
|
||||
|
||||
@@ -72,9 +72,9 @@ function baseOpts(home, cwd, overrides = {}) {
|
||||
return { home, cwd, env: {}, existsSync: () => false, ...overrides };
|
||||
}
|
||||
|
||||
/** `commands/gsd/*.md` stems shipped by the real repo — used so A1's "71
|
||||
/** `commands/msd/*.md` stems shipped by the real repo — used so A1's "71
|
||||
* entries" tracks the real roster instead of a hardcoded, driftable count. */
|
||||
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
|
||||
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'msd');
|
||||
const REAL_STEMS = fs.readdirSync(REAL_COMMANDS_DIR)
|
||||
.filter((f) => f.endsWith('.md'))
|
||||
.map((f) => f.slice(0, -3))
|
||||
@@ -82,13 +82,13 @@ const REAL_STEMS = fs.readdirSync(REAL_COMMANDS_DIR)
|
||||
|
||||
function skillFilesFor(stems) {
|
||||
const files = {};
|
||||
for (const s of stems) files[`skills/gsd-${s}/SKILL.md`] = 'x';
|
||||
for (const s of stems) files[`skills/msd-${s}/SKILL.md`] = 'x';
|
||||
return files;
|
||||
}
|
||||
|
||||
function commandFilesFor(stems) {
|
||||
const files = {};
|
||||
for (const s of stems) files[`commands/gsd-${s}.md`] = 'x';
|
||||
for (const s of stems) files[`commands/msd-${s}.md`] = 'x';
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('buildShadowReport — shape (A1-A6)', () => {
|
||||
assert.strictEqual(report.triggers.length, REAL_STEMS.length);
|
||||
assert.deepStrictEqual(report.winner, { kind: 'skills', scope: 'global' });
|
||||
assert.deepStrictEqual(report.shadowedSide, { kind: 'commands', scope: 'local' });
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger).sort(), REAL_STEMS.map((s) => `gsd-${s}`));
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger).sort(), REAL_STEMS.map((s) => `msd-${s}`));
|
||||
});
|
||||
|
||||
test('no report for a single scope, global only (A2)', () => {
|
||||
@@ -163,8 +163,8 @@ describe('buildShadowReport — shape (A1-A6)', () => {
|
||||
const cwd = '/fixture/a5-cwd';
|
||||
const homes = scopeHomes('windsurf', home, cwd);
|
||||
const byConfigHome = new Map([
|
||||
[homes.global, manifest({ manifestVersion: 2, runtime: 'windsurf', scope: 'global', files: { 'agents/gsd-planner.md': 'a' } })],
|
||||
[homes.local, manifest({ manifestVersion: 2, runtime: 'windsurf', scope: 'local', files: { 'workflows/gsd-plan-phase.md': 'a' } })],
|
||||
[homes.global, manifest({ manifestVersion: 2, runtime: 'windsurf', scope: 'global', files: { 'agents/msd-planner.md': 'a' } })],
|
||||
[homes.local, manifest({ manifestVersion: 2, runtime: 'windsurf', scope: 'local', files: { 'workflows/msd-plan-phase.md': 'a' } })],
|
||||
]);
|
||||
const report = buildShadowReport('windsurf', baseOpts(home, cwd, { readManifest: mkReadManifest(byConfigHome) }));
|
||||
assert.strictEqual(report.shadowed, false);
|
||||
@@ -405,8 +405,8 @@ describe('buildShadowReport — independence across calls (A18)', () => {
|
||||
|
||||
describe('buildShadowReport — production call shape (A19)', () => {
|
||||
test('production call shape resolves, matches the injected-dep rows\' shape', (t) => {
|
||||
const home = createTempDir('gsd-shadow-a19-home-');
|
||||
const cwd = createTempDir('gsd-shadow-a19-cwd-');
|
||||
const home = createTempDir('msd-shadow-a19-home-');
|
||||
const cwd = createTempDir('msd-shadow-a19-cwd-');
|
||||
t.after(() => { cleanup(home); cleanup(cwd); });
|
||||
|
||||
const globalDir = path.join(home, '.claude');
|
||||
@@ -463,7 +463,7 @@ describe('buildShadowReport — per-scope truth filter (A21-A24)', () => {
|
||||
const stems = ['plan-phase', 'milestone-complete', 'phase-create'];
|
||||
const report = buildShadowReport('claude', coexistenceOpts(home, cwd, stems));
|
||||
assert.strictEqual(report.shadowed, true);
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger).sort(), stems.map((s) => `gsd-${s}`).sort());
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger).sort(), stems.map((s) => `msd-${s}`).sort());
|
||||
});
|
||||
|
||||
test('global strict superset of local (full vs core profile): only the intersection is reported (A22)', () => {
|
||||
@@ -479,7 +479,7 @@ describe('buildShadowReport — per-scope truth filter (A21-A24)', () => {
|
||||
assert.strictEqual(report.shadowed, true);
|
||||
// Only 'a' is a REAL local artifact — 'b' and 'c' must never be reported
|
||||
// as shadowed local commands; there is no local artifact for either.
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger), ['gsd-a']);
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger), ['msd-a']);
|
||||
});
|
||||
|
||||
test('local has a stem global does not: not reported as shadowed (A23)', () => {
|
||||
@@ -494,7 +494,7 @@ describe('buildShadowReport — per-scope truth filter (A21-A24)', () => {
|
||||
assert.strictEqual(report.shadowed, true);
|
||||
// 'z' exists ONLY at local (no global artifact "wins" it) — must not
|
||||
// appear in the shadowed set at all.
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger), ['gsd-a']);
|
||||
assert.deepStrictEqual(report.triggers.map((t) => t.trigger), ['msd-a']);
|
||||
});
|
||||
|
||||
test('disjoint stem sets: shadowed is false (A24)', () => {
|
||||
@@ -538,7 +538,7 @@ describe('buildShadowReport — property (F1)', () => {
|
||||
// only reported when a real artifact exists at BOTH scopes.
|
||||
const expectedShadowed = globalStems
|
||||
.filter((s) => localStems.includes(s))
|
||||
.map((s) => `gsd-${s}`)
|
||||
.map((s) => `msd-${s}`)
|
||||
.sort();
|
||||
const actualShadowed = report.triggers.map((t) => t.trigger).sort();
|
||||
assert.deepStrictEqual(actualShadowed, expectedShadowed);
|
||||
@@ -667,19 +667,19 @@ describe('sanitizeForRender — properties (F2, F3)', () => {
|
||||
});
|
||||
|
||||
// ─── E1-E12 — resolveSpecRootReference direct unit coverage ───────────────
|
||||
// Matrix section E (`.gsd/phase/feat-2873-cross-scope-shadowing/50-test-matrix.md`).
|
||||
// Matrix section E (`.msd/phase/feat-2873-cross-scope-shadowing/50-test-matrix.md`).
|
||||
// Only the E13/E14 installed-output integration pair (in
|
||||
// tests/install-runtime-artifacts.test.cjs) and F4's idempotence property
|
||||
// (above) touched this exported function before this block — these rows
|
||||
// exercise it DIRECTLY, one behavior at a time.
|
||||
describe('resolveSpecRootReference — direct unit coverage (E1-E12)', () => {
|
||||
test('global skill body: the include is replaced by the two-step imperative form naming both candidates (E1)', () => {
|
||||
const body = '@~/.claude/gsd-core/workflows/plan-phase.md';
|
||||
const body = '@~/.claude/msd-core/workflows/plan-phase.md';
|
||||
const result = resolveSpecRootReference(body);
|
||||
assert.notStrictEqual(result, body);
|
||||
assert.ok(!result.startsWith('@'), 'the static @-include must be gone');
|
||||
assert.ok(
|
||||
result.includes('.claude/gsd-core/workflows/plan-phase.md') && result.includes('~/.claude/gsd-core/workflows/plan-phase.md'),
|
||||
result.includes('.claude/msd-core/workflows/plan-phase.md') && result.includes('~/.claude/msd-core/workflows/plan-phase.md'),
|
||||
`expected both the project-local and global candidate paths named in: ${JSON.stringify(result)}`,
|
||||
);
|
||||
});
|
||||
@@ -691,7 +691,7 @@ describe('resolveSpecRootReference — direct unit coverage (E1-E12)', () => {
|
||||
// config dir, never `~`, so this never has the `@~/.claude/` shape
|
||||
// WORKFLOW_SPEC_ROOT_INCLUDE_RE requires in the first place — a genuine
|
||||
// non-qualifying condition, not a hand-waved one.
|
||||
const body = '@/Users/dev/myrepo/.claude/gsd-core/workflows/plan-phase.md';
|
||||
const body = '@/Users/dev/myrepo/.claude/msd-core/workflows/plan-phase.md';
|
||||
assert.strictEqual(resolveSpecRootReference(body), body);
|
||||
});
|
||||
|
||||
@@ -699,14 +699,14 @@ describe('resolveSpecRootReference — direct unit coverage (E1-E12)', () => {
|
||||
// Real literal shapes emitted for other runtimes (verified empirically
|
||||
// against a real --cursor --global install): no `.claude/` segment at
|
||||
// all, so none of them ever match the claude-only spec-root regex.
|
||||
const cursorGlobal = '@$HOME/gsd-core/workflows/plan-phase.md';
|
||||
const genericLocal = '@./gsd-core/workflows/plan-phase.md';
|
||||
const cursorGlobal = '@$HOME/msd-core/workflows/plan-phase.md';
|
||||
const genericLocal = '@./msd-core/workflows/plan-phase.md';
|
||||
assert.strictEqual(resolveSpecRootReference(cursorGlobal), cursorGlobal);
|
||||
assert.strictEqual(resolveSpecRootReference(genericLocal), genericLocal);
|
||||
});
|
||||
|
||||
test('a references/ include is a different spec root and stays static (E4)', () => {
|
||||
const body = '@~/.claude/gsd-core/references/ui-brand.md';
|
||||
const body = '@~/.claude/msd-core/references/ui-brand.md';
|
||||
assert.strictEqual(resolveSpecRootReference(body), body);
|
||||
});
|
||||
|
||||
@@ -716,12 +716,12 @@ describe('resolveSpecRootReference — direct unit coverage (E1-E12)', () => {
|
||||
});
|
||||
|
||||
test('an include inside a fenced code block is untouched, byte-identical (E6)', () => {
|
||||
const body = ['```', '@~/.claude/gsd-core/workflows/plan-phase.md', '```'].join('\n');
|
||||
const body = ['```', '@~/.claude/msd-core/workflows/plan-phase.md', '```'].join('\n');
|
||||
assert.strictEqual(resolveSpecRootReference(body), body);
|
||||
});
|
||||
|
||||
test('an include mentioned in inline backticks is untouched, byte-identical (E7)', () => {
|
||||
const body = 'See `@~/.claude/gsd-core/workflows/plan-phase.md` for the spec.';
|
||||
const body = 'See `@~/.claude/msd-core/workflows/plan-phase.md` for the spec.';
|
||||
assert.strictEqual(resolveSpecRootReference(body), body);
|
||||
});
|
||||
|
||||
@@ -732,24 +732,24 @@ describe('resolveSpecRootReference — direct unit coverage (E1-E12)', () => {
|
||||
|
||||
test('two independent workflow includes both resolve (E9)', () => {
|
||||
const body = [
|
||||
'@~/.claude/gsd-core/workflows/plan-phase.md',
|
||||
'@~/.claude/gsd-core/workflows/execute-phase.md',
|
||||
'@~/.claude/msd-core/workflows/plan-phase.md',
|
||||
'@~/.claude/msd-core/workflows/execute-phase.md',
|
||||
].join('\n');
|
||||
const result = resolveSpecRootReference(body);
|
||||
assert.ok(!result.includes('@~/.claude/gsd-core/workflows/plan-phase.md'));
|
||||
assert.ok(!result.includes('@~/.claude/gsd-core/workflows/execute-phase.md'));
|
||||
assert.ok(result.includes('.claude/gsd-core/workflows/plan-phase.md'));
|
||||
assert.ok(result.includes('.claude/gsd-core/workflows/execute-phase.md'));
|
||||
assert.ok(!result.includes('@~/.claude/msd-core/workflows/plan-phase.md'));
|
||||
assert.ok(!result.includes('@~/.claude/msd-core/workflows/execute-phase.md'));
|
||||
assert.ok(result.includes('.claude/msd-core/workflows/plan-phase.md'));
|
||||
assert.ok(result.includes('.claude/msd-core/workflows/execute-phase.md'));
|
||||
});
|
||||
|
||||
test('prose merely mentioning gsd-core/workflows/x.md is untouched (E10)', () => {
|
||||
const body = 'See gsd-core/workflows/plan-phase.md for background on how this works.';
|
||||
test('prose merely mentioning msd-core/workflows/x.md is untouched (E10)', () => {
|
||||
const body = 'See msd-core/workflows/plan-phase.md for background on how this works.';
|
||||
assert.strictEqual(resolveSpecRootReference(body), body);
|
||||
});
|
||||
|
||||
test('a CRLF body emits identically to LF, no orphaned \\r (E11)', () => {
|
||||
const bodyLf = '@~/.claude/gsd-core/workflows/plan-phase.md\nSecond line.';
|
||||
const bodyCrlf = '@~/.claude/gsd-core/workflows/plan-phase.md\r\nSecond line.';
|
||||
const bodyLf = '@~/.claude/msd-core/workflows/plan-phase.md\nSecond line.';
|
||||
const bodyCrlf = '@~/.claude/msd-core/workflows/plan-phase.md\r\nSecond line.';
|
||||
const resultLf = resolveSpecRootReference(bodyLf);
|
||||
const resultCrlf = resolveSpecRootReference(bodyCrlf);
|
||||
assert.strictEqual(resultCrlf, resultLf.replace('\n', '\r\n'));
|
||||
@@ -760,7 +760,7 @@ describe('resolveSpecRootReference — direct unit coverage (E1-E12)', () => {
|
||||
});
|
||||
|
||||
test('applying the transform twice over an installed tree is idempotent (E12)', () => {
|
||||
const body = 'intro\n@~/.claude/gsd-core/workflows/plan-phase.md\noutro';
|
||||
const body = 'intro\n@~/.claude/msd-core/workflows/plan-phase.md\noutro';
|
||||
const once = resolveSpecRootReference(body);
|
||||
const twice = resolveSpecRootReference(once);
|
||||
assert.strictEqual(twice, once);
|
||||
@@ -780,9 +780,9 @@ describe('resolveSpecRootReference — unit (fence detection, #2873 review Findi
|
||||
test('mismatched fence types (``` opened, ~~~ inside, ``` closes) leave BOTH includes untouched', () => {
|
||||
const body = [
|
||||
'```',
|
||||
'@~/.claude/gsd-core/workflows/alpha.md',
|
||||
'@~/.claude/msd-core/workflows/alpha.md',
|
||||
'~~~',
|
||||
'@~/.claude/gsd-core/workflows/beta.md',
|
||||
'@~/.claude/msd-core/workflows/beta.md',
|
||||
'```',
|
||||
].join('\n');
|
||||
|
||||
@@ -796,7 +796,7 @@ describe('resolveSpecRootReference — unit (fence detection, #2873 review Findi
|
||||
const body = [
|
||||
'````',
|
||||
'```',
|
||||
'@~/.claude/gsd-core/workflows/nested.md',
|
||||
'@~/.claude/msd-core/workflows/nested.md',
|
||||
'```',
|
||||
'````',
|
||||
].join('\n');
|
||||
@@ -809,7 +809,7 @@ describe('resolveSpecRootReference — unit (fence detection, #2873 review Findi
|
||||
test('an unterminated fence covers to end-of-string, leaving the include untouched', () => {
|
||||
const body = [
|
||||
'```',
|
||||
'@~/.claude/gsd-core/workflows/orphan.md',
|
||||
'@~/.claude/msd-core/workflows/orphan.md',
|
||||
].join('\n');
|
||||
|
||||
const result = resolveSpecRootReference(body);
|
||||
@@ -820,9 +820,9 @@ describe('resolveSpecRootReference — unit (fence detection, #2873 review Findi
|
||||
test('a fence opened with a longer run (````) is NOT closed by a shorter run (```)', () => {
|
||||
const body = [
|
||||
'````',
|
||||
'@~/.claude/gsd-core/workflows/longshort.md',
|
||||
'@~/.claude/msd-core/workflows/longshort.md',
|
||||
'```',
|
||||
'@~/.claude/gsd-core/workflows/other.md',
|
||||
'@~/.claude/msd-core/workflows/other.md',
|
||||
'````',
|
||||
].join('\n');
|
||||
|
||||
@@ -841,7 +841,7 @@ describe('resolveSpecRootReference — unit (fence detection, #2873 review Findi
|
||||
describe('resolveSpecRootReference — property (F4)', () => {
|
||||
test('spec-root transform is idempotent over arbitrary bodies', () => {
|
||||
// A bare fc.string() body would almost never contain the exact
|
||||
// `@~/.claude/gsd-core/workflows/<stem>.md` shape `resolveSpecRootReference`
|
||||
// `@~/.claude/msd-core/workflows/<stem>.md` shape `resolveSpecRootReference`
|
||||
// matches, making the property vacuous (see this suite's F4 comment and
|
||||
// CONTRIBUTING's writer-seeded-vs-document-shaped generator guidance).
|
||||
// Instead, bodies are assembled from chunks that actually exercise every
|
||||
@@ -849,14 +849,14 @@ describe('resolveSpecRootReference — property (F4)', () => {
|
||||
// block wrapping the SAME include shape (left untouched — Claude Code
|
||||
// documents backticks as the way to prevent an `@`-import), a
|
||||
// `@.planning/…` include (a different spec root, untouched), plain prose
|
||||
// that merely MENTIONS `gsd-core/workflows/<stem>.md` without the
|
||||
// that merely MENTIONS `msd-core/workflows/<stem>.md` without the
|
||||
// line-start `@` (untouched), and arbitrary free text.
|
||||
const stemArb = fc.stringMatching(/^[a-z][a-z0-9._-]{0,20}$/);
|
||||
const includeLineArb = stemArb.map((s) => `@~/.claude/gsd-core/workflows/${s}.md`);
|
||||
const proseMentionArb = stemArb.map((s) => `See gsd-core/workflows/${s}.md for background.`);
|
||||
const includeLineArb = stemArb.map((s) => `@~/.claude/msd-core/workflows/${s}.md`);
|
||||
const proseMentionArb = stemArb.map((s) => `See msd-core/workflows/${s}.md for background.`);
|
||||
const planningIncludeArb = stemArb.map((s) => `@.planning/${s}.md`);
|
||||
const fencedIncludeArb = fc.tuple(fc.constantFrom('```', '~~~'), stemArb).map(
|
||||
([fence, s]) => `${fence}\n@~/.claude/gsd-core/workflows/${s}.md\n${fence}`,
|
||||
([fence, s]) => `${fence}\n@~/.claude/msd-core/workflows/${s}.md\n${fence}`,
|
||||
);
|
||||
// #2873 review Finding 1: a same-type-only generator is structurally
|
||||
// incapable of producing the mismatched-delimiter defect the review
|
||||
@@ -870,13 +870,13 @@ describe('resolveSpecRootReference — property (F4)', () => {
|
||||
stemArb,
|
||||
).map(
|
||||
([[openFence, midFence], s1, s2]) =>
|
||||
`${openFence}\n@~/.claude/gsd-core/workflows/${s1}.md\n${midFence}\n@~/.claude/gsd-core/workflows/${s2}.md\n${openFence}`,
|
||||
`${openFence}\n@~/.claude/msd-core/workflows/${s1}.md\n${midFence}\n@~/.claude/msd-core/workflows/${s2}.md\n${openFence}`,
|
||||
);
|
||||
const nestedFencedIncludeArb = fc.tuple(fc.constantFrom('```', '~~~'), stemArb).map(
|
||||
([fenceChar, s]) => {
|
||||
const inner = fenceChar.repeat(3);
|
||||
const outer = fenceChar.repeat(4);
|
||||
return `${outer}\n${inner}\n@~/.claude/gsd-core/workflows/${s}.md\n${inner}\n${outer}`;
|
||||
return `${outer}\n${inner}\n@~/.claude/msd-core/workflows/${s}.md\n${inner}\n${outer}`;
|
||||
},
|
||||
);
|
||||
const plainTextArb = fc.string({ maxLength: 40 });
|
||||
|
||||
Reference in New Issue
Block a user