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:
@@ -27,13 +27,13 @@ const fc = require('fast-check');
|
||||
|
||||
// ─── module under test ────────────────────────────────────────────────────────
|
||||
|
||||
const configLoader = require('../gsd-core/bin/lib/config-loader.cjs');
|
||||
const configLoader = require('../msd-core/bin/lib/config-loader.cjs');
|
||||
|
||||
const { loadConfig, loadConfigResolved, _resetRuntimeWarningCacheForTests, _deepMergeConfig } = configLoader;
|
||||
|
||||
// ─── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function makeTempProject(prefix = 'gsd-cfg-loader-test-') {
|
||||
function makeTempProject(prefix = 'msd-cfg-loader-test-') {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases'), { recursive: true });
|
||||
return tmpDir;
|
||||
@@ -218,25 +218,25 @@ describe('loadConfig — unknown-key warning dedup', () => {
|
||||
});
|
||||
|
||||
test('unknown key produces a warning mentioning the key name', () => {
|
||||
writeConfig(tmpDir, { __gsd_unknown_sentinel__: true });
|
||||
writeConfig(tmpDir, { __msd_unknown_sentinel__: true });
|
||||
loadConfig(tmpDir);
|
||||
const warnings = stderrLines.filter(l => l.includes('__gsd_unknown_sentinel__'));
|
||||
const warnings = stderrLines.filter(l => l.includes('__msd_unknown_sentinel__'));
|
||||
assert.ok(warnings.length >= 1, 'should warn about unknown key');
|
||||
});
|
||||
|
||||
test('calling loadConfig twice does not double-emit the same unknown-key warning', () => {
|
||||
writeConfig(tmpDir, { __gsd_dedup_test__: true });
|
||||
writeConfig(tmpDir, { __msd_dedup_test__: true });
|
||||
loadConfig(tmpDir);
|
||||
loadConfig(tmpDir);
|
||||
const warnings = stderrLines.filter(l => l.includes('__gsd_dedup_test__'));
|
||||
const warnings = stderrLines.filter(l => l.includes('__msd_dedup_test__'));
|
||||
// Should appear at most once
|
||||
assert.ok(warnings.length <= 1, `warning emitted more than once: ${warnings.length} times`);
|
||||
});
|
||||
|
||||
test('agent_tools is accepted without an unknown-key warning (#4032)', () => {
|
||||
writeConfig(tmpDir, { agent_tools: { 'gsd-executor': ['WebFetch'] } });
|
||||
writeConfig(tmpDir, { agent_tools: { 'msd-executor': ['WebFetch'] } });
|
||||
const config = loadConfig(tmpDir);
|
||||
assert.deepEqual(config.agent_tools, { 'gsd-executor': ['WebFetch'] });
|
||||
assert.deepEqual(config.agent_tools, { 'msd-executor': ['WebFetch'] });
|
||||
assert.equal(
|
||||
stderrLines.some((line) => line.includes('agent_tools')),
|
||||
false,
|
||||
@@ -254,7 +254,7 @@ describe('loadConfig — unknown-key warning dedup', () => {
|
||||
// Asserts on the exported Set rather than stderr prose (CONTRIBUTING.md —
|
||||
// Prohibited: Raw Text Matching on Test Outputs).
|
||||
test('_resetRuntimeWarningCacheForTests clears the unknown-key dedup set', () => {
|
||||
writeConfig(tmpDir, { __gsd_reset_probe__: true });
|
||||
writeConfig(tmpDir, { __msd_reset_probe__: true });
|
||||
loadConfig(tmpDir);
|
||||
assert.ok(
|
||||
configLoader._warnedUnknownConfigKeys.size > 0,
|
||||
@@ -417,15 +417,15 @@ describe('loadConfigResolved — provenance', () => {
|
||||
assert.ok('model_profile' in result.config);
|
||||
});
|
||||
|
||||
test('source is "global-defaults" when no .planning exists but ~/.gsd/defaults.json readable', () => {
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-home-test-'));
|
||||
const origGsdHome = process.env['GSD_HOME'];
|
||||
test('source is "global-defaults" when no .planning exists but ~/.msd/defaults.json readable', () => {
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-home-test-'));
|
||||
const origMsdHome = process.env['MSD_HOME'];
|
||||
try {
|
||||
const gsdDir = path.join(homeTmp, '.gsd');
|
||||
fs.mkdirSync(gsdDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(gsdDir, 'defaults.json'), JSON.stringify({ model_profile: 'home-defaults' }), 'utf-8');
|
||||
process.env['GSD_HOME'] = homeTmp;
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-noplanning-'));
|
||||
const msdDir = path.join(homeTmp, '.msd');
|
||||
fs.mkdirSync(msdDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(msdDir, 'defaults.json'), JSON.stringify({ model_profile: 'home-defaults' }), 'utf-8');
|
||||
process.env['MSD_HOME'] = homeTmp;
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-noplanning-'));
|
||||
try {
|
||||
const result = loadConfigResolved(noPlanning);
|
||||
assert.equal(result.source, 'global-defaults');
|
||||
@@ -435,26 +435,26 @@ describe('loadConfigResolved — provenance', () => {
|
||||
cleanup(noPlanning);
|
||||
}
|
||||
} finally {
|
||||
if (origGsdHome === undefined) delete process.env['GSD_HOME'];
|
||||
else process.env['GSD_HOME'] = origGsdHome;
|
||||
if (origMsdHome === undefined) delete process.env['MSD_HOME'];
|
||||
else process.env['MSD_HOME'] = origMsdHome;
|
||||
cleanup(homeTmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('source is "builtin-defaults" when no .planning and no global defaults', () => {
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-noplanning2-'));
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-nohome-'));
|
||||
const origGsdHome = process.env['GSD_HOME'];
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-noplanning2-'));
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-nohome-'));
|
||||
const origMsdHome = process.env['MSD_HOME'];
|
||||
try {
|
||||
// Point GSD_HOME to a directory with no .gsd/defaults.json
|
||||
process.env['GSD_HOME'] = homeTmp;
|
||||
// Point MSD_HOME to a directory with no .msd/defaults.json
|
||||
process.env['MSD_HOME'] = homeTmp;
|
||||
const result = loadConfigResolved(noPlanning);
|
||||
assert.equal(result.source, 'builtin-defaults');
|
||||
assert.equal(result.degraded, false);
|
||||
assert.ok('model_profile' in result.config);
|
||||
} finally {
|
||||
if (origGsdHome === undefined) delete process.env['GSD_HOME'];
|
||||
else process.env['GSD_HOME'] = origGsdHome;
|
||||
if (origMsdHome === undefined) delete process.env['MSD_HOME'];
|
||||
else process.env['MSD_HOME'] = origMsdHome;
|
||||
cleanup(noPlanning);
|
||||
cleanup(homeTmp);
|
||||
}
|
||||
@@ -501,32 +501,32 @@ describe('loadConfigResolved — provenance', () => {
|
||||
assert.equal(explicit.source, 'root');
|
||||
assert.equal(explicit.degraded, false);
|
||||
|
||||
const original = process.env.GSD_WORKSTREAM;
|
||||
const original = process.env.MSD_WORKSTREAM;
|
||||
try {
|
||||
process.env.GSD_WORKSTREAM = workstream;
|
||||
process.env.MSD_WORKSTREAM = workstream;
|
||||
const ambient = loadConfigResolved(tmpDir);
|
||||
assert.equal(ambient.source, 'root');
|
||||
assert.equal(ambient.degraded, false);
|
||||
} finally {
|
||||
if (original === undefined) delete process.env.GSD_WORKSTREAM;
|
||||
else process.env.GSD_WORKSTREAM = original;
|
||||
if (original === undefined) delete process.env.MSD_WORKSTREAM;
|
||||
else process.env.MSD_WORKSTREAM = original;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Fix 2a: GSD_WORKSTREAM set to nonexistent workstream (dir absent) → source:"root", degraded:true', () => {
|
||||
test('Fix 2a: MSD_WORKSTREAM set to nonexistent workstream (dir absent) → source:"root", degraded:true', () => {
|
||||
writeConfig(tmpDir, { model_profile: 'root-value' });
|
||||
const origWs = process.env['GSD_WORKSTREAM'];
|
||||
const origWs = process.env['MSD_WORKSTREAM'];
|
||||
try {
|
||||
process.env['GSD_WORKSTREAM'] = 'nonexistent-ws';
|
||||
process.env['MSD_WORKSTREAM'] = 'nonexistent-ws';
|
||||
// Do NOT create the workstream directory
|
||||
const result = loadConfigResolved(tmpDir);
|
||||
assert.equal(result.source, 'root', 'nonexistent workstream should fall back to source:"root"');
|
||||
assert.equal(result.degraded, true, 'should be degraded when workstream dir is absent');
|
||||
assert.equal(result.config.model_profile, 'root-value', 'config should equal root config');
|
||||
} finally {
|
||||
if (origWs === undefined) delete process.env['GSD_WORKSTREAM'];
|
||||
else process.env['GSD_WORKSTREAM'] = origWs;
|
||||
if (origWs === undefined) delete process.env['MSD_WORKSTREAM'];
|
||||
else process.env['MSD_WORKSTREAM'] = origWs;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -603,7 +603,7 @@ const path = require('path');
|
||||
const { createTempProject, cleanup } = require('./helpers.cjs');
|
||||
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
|
||||
|
||||
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
|
||||
const { loadConfig } = require('../msd-core/bin/lib/config-loader.cjs');
|
||||
|
||||
function makeSubRepo(parent, name) {
|
||||
const dir = path.join(parent, name);
|
||||
@@ -776,17 +776,17 @@ const { runNode } = require('./helpers/process-seam.cjs');
|
||||
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
|
||||
/**
|
||||
* Run gsd-tools and return { stdout, stderr, status }.
|
||||
* Run msd-tools and return { stdout, stderr, status }.
|
||||
* Always captures stderr even when exit code is 0.
|
||||
*/
|
||||
function runWithStderr(args, cwd, env = {}) {
|
||||
const result = runNode([TOOLS_PATH, ...args], {
|
||||
cwd,
|
||||
// #3532: pin GSD_HOME to an empty sandbox so a developer's real
|
||||
// ~/.gsd/defaults.json cannot leak shadow-key warnings into children that
|
||||
// #3532: pin MSD_HOME to an empty sandbox so a developer's real
|
||||
// ~/.msd/defaults.json cannot leak shadow-key warnings into children that
|
||||
// these suites assert are stderr-clean (TEST_ENV_BASE only BLANKS the
|
||||
// var; an empty string falls through to the real homedir).
|
||||
env: { ...process.env, ...TEST_ENV_BASE, GSD_HOME: installSpawnHome(), ...env },
|
||||
env: { ...process.env, ...TEST_ENV_BASE, MSD_HOME: installSpawnHome(), ...env },
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
});
|
||||
return {
|
||||
@@ -807,7 +807,7 @@ describe('bug-3523 — no warning for legacy top-level branching_strategy', () =
|
||||
});
|
||||
|
||||
test('loadConfig emits no stderr when config.json has top-level branching_strategy', () => {
|
||||
tmpDir = createTempProject('gsd-3523-warn-');
|
||||
tmpDir = createTempProject('msd-3523-warn-');
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
@@ -829,7 +829,7 @@ describe('bug-3523 — no warning for legacy top-level branching_strategy', () =
|
||||
});
|
||||
|
||||
test('branching_strategy value is still surfaced after loadConfig on legacy shape', () => {
|
||||
tmpDir = createTempProject('gsd-3523-value-');
|
||||
tmpDir = createTempProject('msd-3523-value-');
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
@@ -885,13 +885,13 @@ describe('bug-3523 — double-emission reduced to single-emission', () => {
|
||||
// fixed) to verify the deduplication guard works for other keys too.
|
||||
// We verify that the count of warning lines for a single unknown key is
|
||||
// exactly once — not zero and not two — even if loadConfig is invoked twice internally.
|
||||
tmpDir = createTempProject('gsd-3523-dedup-');
|
||||
tmpDir = createTempProject('msd-3523-dedup-');
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
// intentionally_unknown_key_for_dedup_test: a key that can never be valid
|
||||
__gsd3523_dedup_sentinel__: true,
|
||||
__msd3523_dedup_sentinel__: true,
|
||||
}, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
@@ -906,7 +906,7 @@ describe('bug-3523 — double-emission reduced to single-emission', () => {
|
||||
// Count how many times the sentinel key appears in warnings
|
||||
const warningLines = result.stderr
|
||||
.split('\n')
|
||||
.filter(l => l.includes('__gsd3523_dedup_sentinel__'));
|
||||
.filter(l => l.includes('__msd3523_dedup_sentinel__'));
|
||||
|
||||
assert.equal(
|
||||
warningLines.length,
|
||||
@@ -928,7 +928,7 @@ describe('bug-3523 — option 3 on-disk migration of branching_strategy', () =>
|
||||
});
|
||||
|
||||
test('after loadConfig, on-disk config.json has branching_strategy under git.*', () => {
|
||||
tmpDir = createTempProject('gsd-3523-writeback-');
|
||||
tmpDir = createTempProject('msd-3523-writeback-');
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
@@ -959,7 +959,7 @@ describe('bug-3523 — option 3 on-disk migration of branching_strategy', () =>
|
||||
test('migration does not clobber existing git.branching_strategy', () => {
|
||||
// If git.branching_strategy is already set, the top-level value should
|
||||
// not overwrite it (nested wins, matching SDK mergeDefaults precedence).
|
||||
tmpDir = createTempProject('gsd-3523-no-clobber-');
|
||||
tmpDir = createTempProject('msd-3523-no-clobber-');
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
@@ -991,7 +991,7 @@ describe('bug-3523 — option 3 on-disk migration of branching_strategy', () =>
|
||||
});
|
||||
|
||||
test('workstream load also self-heals legacy root branching_strategy', () => {
|
||||
tmpDir = createTempProject('gsd-3523-workstream-root-');
|
||||
tmpDir = createTempProject('msd-3523-workstream-root-');
|
||||
const rootConfigPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
const workstreamDir = path.join(tmpDir, '.planning', 'workstreams', 'alpha');
|
||||
fs.mkdirSync(workstreamDir, { recursive: true });
|
||||
@@ -1010,7 +1010,7 @@ describe('bug-3523 — option 3 on-disk migration of branching_strategy', () =>
|
||||
);
|
||||
|
||||
const triggerResult = runWithStderr(['resolve-model', 'planner'], tmpDir, {
|
||||
GSD_WORKSTREAM: 'alpha',
|
||||
MSD_WORKSTREAM: 'alpha',
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
@@ -1071,7 +1071,7 @@ describe('bug-3523 — CJS↔SDK contract: both agree on legacy branching_strate
|
||||
});
|
||||
|
||||
test('CJS loadConfig surfaces branching_strategy matching SDK mergeDefaults behavior', () => {
|
||||
tmpDir = createTempProject('gsd-3523-parity-');
|
||||
tmpDir = createTempProject('msd-3523-parity-');
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
// The fixture that the SDK's mergeDefaults handles correctly (PR #3116).
|
||||
fs.writeFileSync(
|
||||
@@ -1286,7 +1286,7 @@ describe('#2997: phase_id_convention is not silently dropped on a clean read', (
|
||||
const cfgPath = (dir) => path.join(dir, '.planning', 'config.json');
|
||||
|
||||
test('setting phase_id_convention in config.json survives into the resolved config', () => {
|
||||
const tmpDir = createTempDir('gsd-2997-');
|
||||
const tmpDir = createTempDir('msd-2997-');
|
||||
try {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(cfgPath(tmpDir), JSON.stringify({ phase_id_convention: 'milestone-prefixed' }), 'utf-8');
|
||||
@@ -1298,7 +1298,7 @@ describe('#2997: phase_id_convention is not silently dropped on a clean read', (
|
||||
});
|
||||
|
||||
test('phase_id_convention set to null round-trips correctly', () => {
|
||||
const tmpDir = createTempDir('gsd-2997-null-');
|
||||
const tmpDir = createTempDir('msd-2997-null-');
|
||||
try {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(cfgPath(tmpDir), JSON.stringify({ phase_id_convention: null }), 'utf-8');
|
||||
@@ -1309,7 +1309,7 @@ describe('#2997: phase_id_convention is not silently dropped on a clean read', (
|
||||
});
|
||||
|
||||
test('phase_id_convention absent → null in resolved config (no false default)', () => {
|
||||
const tmpDir = createTempDir('gsd-2997-absent-');
|
||||
const tmpDir = createTempDir('msd-2997-absent-');
|
||||
try {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(cfgPath(tmpDir), JSON.stringify({ commit_docs: true }), 'utf-8');
|
||||
@@ -1326,7 +1326,7 @@ describe('#2997: phase_id_convention is not silently dropped on a clean read', (
|
||||
// exists. Under a project .planning/config.json (Branch A — every real project)
|
||||
// the global file is never opened, so each of these set globally is silently
|
||||
// inert. `effort` is deliberately absent: the install-time effort sync
|
||||
// (readGsdEffectiveEffortConfig) DOES merge the global file, so warning on it
|
||||
// (readMsdEffectiveEffortConfig) DOES merge the global file, so warning on it
|
||||
// would be false for the channel users control via effort sync.
|
||||
const GLOBAL_KEYS_SHADOWED_UNDER_PROJECT = [
|
||||
'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
|
||||
@@ -1339,42 +1339,42 @@ const GLOBAL_KEYS_SHADOWED_UNDER_PROJECT = [
|
||||
|
||||
describe('#3532 shadowed global-defaults warning', () => {
|
||||
let tmpDir;
|
||||
let gsdHome;
|
||||
let msdHome;
|
||||
let stderrLines;
|
||||
let originalStderrWrite;
|
||||
let originalGsdHome;
|
||||
let originalMsdHome;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = makeTempProject('gsd-3532-shadow-');
|
||||
gsdHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3532-home-'));
|
||||
tmpDir = makeTempProject('msd-3532-shadow-');
|
||||
msdHome = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3532-home-'));
|
||||
stderrLines = [];
|
||||
originalStderrWrite = process.stderr.write.bind(process.stderr);
|
||||
process.stderr.write = (chunk) => { stderrLines.push(String(chunk)); return true; };
|
||||
originalGsdHome = process.env.GSD_HOME;
|
||||
process.env.GSD_HOME = gsdHome;
|
||||
originalMsdHome = process.env.MSD_HOME;
|
||||
process.env.MSD_HOME = msdHome;
|
||||
if (_resetRuntimeWarningCacheForTests) _resetRuntimeWarningCacheForTests();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.stderr.write = originalStderrWrite;
|
||||
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
||||
else process.env.GSD_HOME = originalGsdHome;
|
||||
if (originalMsdHome === undefined) delete process.env.MSD_HOME;
|
||||
else process.env.MSD_HOME = originalMsdHome;
|
||||
if (tmpDir) cleanup(tmpDir);
|
||||
if (gsdHome) cleanup(gsdHome);
|
||||
tmpDir = gsdHome = null;
|
||||
if (msdHome) cleanup(msdHome);
|
||||
tmpDir = msdHome = null;
|
||||
});
|
||||
|
||||
function writeGlobalDefaults(obj) {
|
||||
fs.mkdirSync(path.join(gsdHome, '.gsd'), { recursive: true });
|
||||
fs.mkdirSync(path.join(msdHome, '.msd'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(gsdHome, '.gsd', 'defaults.json'),
|
||||
path.join(msdHome, '.msd', 'defaults.json'),
|
||||
JSON.stringify(obj, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
test('project config + global model keys -> one warning naming both keys', () => {
|
||||
writeConfig(tmpDir, { model_profile: 'balanced' });
|
||||
writeGlobalDefaults({ model_overrides: { 'gsd-executor': 'haiku' }, model_profile: 'quality' });
|
||||
writeGlobalDefaults({ model_overrides: { 'msd-executor': 'haiku' }, model_profile: 'quality' });
|
||||
loadConfigResolved(tmpDir);
|
||||
const warnings = stderrLines.filter(l => l.includes('model_overrides') && l.includes('model_profile'));
|
||||
assert.equal(warnings.length, 1, `expected exactly one shadowed-keys warning, got: ${stderrLines.join('')}`);
|
||||
@@ -1404,7 +1404,7 @@ describe('#3532 shadowed global-defaults warning', () => {
|
||||
});
|
||||
|
||||
test('bare dir without .planning honors global defaults without warning (Branch D)', () => {
|
||||
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3532-bare-'));
|
||||
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3532-bare-'));
|
||||
try {
|
||||
writeGlobalDefaults({ model_profile: 'quality' });
|
||||
const resolution = loadConfigResolved(bare);
|
||||
@@ -1418,8 +1418,8 @@ describe('#3532 shadowed global-defaults warning', () => {
|
||||
|
||||
test('unparseable global defaults skip the shadow warning', () => {
|
||||
writeConfig(tmpDir, { model_profile: 'balanced' });
|
||||
fs.mkdirSync(path.join(gsdHome, '.gsd'), { recursive: true });
|
||||
fs.writeFileSync(path.join(gsdHome, '.gsd', 'defaults.json'), '{not json');
|
||||
fs.mkdirSync(path.join(msdHome, '.msd'), { recursive: true });
|
||||
fs.writeFileSync(path.join(msdHome, '.msd', 'defaults.json'), '{not json');
|
||||
loadConfigResolved(tmpDir);
|
||||
assert.equal(stderrLines.filter(l => l.includes('shadowed')).length, 0);
|
||||
});
|
||||
@@ -1434,9 +1434,9 @@ describe('#3532 shadowed global-defaults warning', () => {
|
||||
|
||||
test('non-resolution global keys do not warn from this check', () => {
|
||||
writeConfig(tmpDir, { model_profile: 'balanced' });
|
||||
writeGlobalDefaults({ __gsd_3532_arbitrary__: true });
|
||||
writeGlobalDefaults({ __msd_3532_arbitrary__: true });
|
||||
loadConfigResolved(tmpDir);
|
||||
assert.equal(stderrLines.filter(l => l.includes('__gsd_3532_arbitrary__') && l.includes('shadowed')).length, 0);
|
||||
assert.equal(stderrLines.filter(l => l.includes('__msd_3532_arbitrary__') && l.includes('shadowed')).length, 0);
|
||||
});
|
||||
|
||||
// Typed-IR parity canary (CONTRIBUTING: assert the exported dedup Set, not
|
||||
@@ -1508,7 +1508,7 @@ describe('regressions — #3760 loader never expands or persists a non-object se
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = makeTempProject('gsd-3760-loader-');
|
||||
tmpDir = makeTempProject('msd-3760-loader-');
|
||||
_resetRuntimeWarningCacheForTests();
|
||||
});
|
||||
|
||||
@@ -1597,7 +1597,7 @@ describe('regressions — #3760 loader never expands or persists a non-object se
|
||||
const {
|
||||
_resetUnusableInputWarningsForTests,
|
||||
_unusableInputEmissionCountForTests,
|
||||
} = require('../gsd-core/bin/lib/unusable-input.cjs');
|
||||
} = require('../msd-core/bin/lib/unusable-input.cjs');
|
||||
|
||||
function emissionsDuring(fn) {
|
||||
const before = _unusableInputEmissionCountForTests();
|
||||
@@ -1636,19 +1636,19 @@ describe('regressions — #3760 loader never expands or persists a non-object se
|
||||
// ─── #3894: workflow.research_before_questions resolves from global defaults ──
|
||||
|
||||
describe('#3894 research_before_questions global-defaults forwarding', () => {
|
||||
test('nested workflow.research_before_questions in ~/.gsd/defaults.json resolves', () => {
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3894-home-'));
|
||||
const origGsdHome = process.env['GSD_HOME'];
|
||||
test('nested workflow.research_before_questions in ~/.msd/defaults.json resolves', () => {
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3894-home-'));
|
||||
const origMsdHome = process.env['MSD_HOME'];
|
||||
try {
|
||||
const gsdDir = path.join(homeTmp, '.gsd');
|
||||
fs.mkdirSync(gsdDir, { recursive: true });
|
||||
const msdDir = path.join(homeTmp, '.msd');
|
||||
fs.mkdirSync(msdDir, { recursive: true });
|
||||
// The reporter's exact shape: same nesting as the forwarded
|
||||
// workflow.post_planning_gaps, one resolves and one did not.
|
||||
fs.writeFileSync(path.join(gsdDir, 'defaults.json'), JSON.stringify({
|
||||
fs.writeFileSync(path.join(msdDir, 'defaults.json'), JSON.stringify({
|
||||
workflow: { post_planning_gaps: true, research_before_questions: true },
|
||||
}), 'utf-8');
|
||||
process.env['GSD_HOME'] = homeTmp;
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3894-noplanning-'));
|
||||
process.env['MSD_HOME'] = homeTmp;
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3894-noplanning-'));
|
||||
try {
|
||||
const result = loadConfigResolved(noPlanning);
|
||||
assert.equal(result.config.post_planning_gaps, true, 'control: the already-forwarded key resolves');
|
||||
@@ -1660,21 +1660,21 @@ describe('#3894 research_before_questions global-defaults forwarding', () => {
|
||||
cleanup(noPlanning);
|
||||
}
|
||||
} finally {
|
||||
if (origGsdHome === undefined) delete process.env['GSD_HOME'];
|
||||
else process.env['GSD_HOME'] = origGsdHome;
|
||||
if (origMsdHome === undefined) delete process.env['MSD_HOME'];
|
||||
else process.env['MSD_HOME'] = origMsdHome;
|
||||
cleanup(homeTmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('flat top-level research_before_questions in ~/.gsd/defaults.json also resolves (Branch D alias parity)', () => {
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3894b-home-'));
|
||||
const origGsdHome = process.env['GSD_HOME'];
|
||||
test('flat top-level research_before_questions in ~/.msd/defaults.json also resolves (Branch D alias parity)', () => {
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3894b-home-'));
|
||||
const origMsdHome = process.env['MSD_HOME'];
|
||||
try {
|
||||
const gsdDir = path.join(homeTmp, '.gsd');
|
||||
fs.mkdirSync(gsdDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(gsdDir, 'defaults.json'), JSON.stringify({ research_before_questions: true }), 'utf-8');
|
||||
process.env['GSD_HOME'] = homeTmp;
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3894b-noplanning-'));
|
||||
const msdDir = path.join(homeTmp, '.msd');
|
||||
fs.mkdirSync(msdDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(msdDir, 'defaults.json'), JSON.stringify({ research_before_questions: true }), 'utf-8');
|
||||
process.env['MSD_HOME'] = homeTmp;
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3894b-noplanning-'));
|
||||
try {
|
||||
const result = loadConfigResolved(noPlanning);
|
||||
assert.equal(result.config.research_before_questions, true);
|
||||
@@ -1682,56 +1682,56 @@ describe('#3894 research_before_questions global-defaults forwarding', () => {
|
||||
cleanup(noPlanning);
|
||||
}
|
||||
} finally {
|
||||
if (origGsdHome === undefined) delete process.env['GSD_HOME'];
|
||||
else process.env['GSD_HOME'] = origGsdHome;
|
||||
if (origMsdHome === undefined) delete process.env['MSD_HOME'];
|
||||
else process.env['MSD_HOME'] = origMsdHome;
|
||||
cleanup(homeTmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('unset resolves to the documented default (false), never undefined', () => {
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3894c-'));
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3894c-home-'));
|
||||
const origGsdHome = process.env['GSD_HOME'];
|
||||
const noPlanning = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3894c-'));
|
||||
const homeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3894c-home-'));
|
||||
const origMsdHome = process.env['MSD_HOME'];
|
||||
try {
|
||||
process.env['GSD_HOME'] = homeTmp; // no .gsd/defaults.json — builtin defaults
|
||||
process.env['MSD_HOME'] = homeTmp; // no .msd/defaults.json — builtin defaults
|
||||
const result = loadConfigResolved(noPlanning);
|
||||
assert.equal(result.config.research_before_questions, false, 'CANONICAL_CONFIG_DEFAULTS.workflow.research_before_questions is false');
|
||||
} finally {
|
||||
if (origGsdHome === undefined) delete process.env['GSD_HOME'];
|
||||
else process.env['GSD_HOME'] = origGsdHome;
|
||||
if (origMsdHome === undefined) delete process.env['MSD_HOME'];
|
||||
else process.env['MSD_HOME'] = origMsdHome;
|
||||
cleanup(homeTmp);
|
||||
cleanup(noPlanning);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── #4717 — an empty config.runtime is filled from GSD_RUNTIME / the marker ──
|
||||
// ── #4717 — an empty config.runtime is filled from MSD_RUNTIME / the marker ──
|
||||
describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
const slash = require('../gsd-core/bin/lib/runtime-slash.cjs');
|
||||
const slash = require('../msd-core/bin/lib/runtime-slash.cjs');
|
||||
const fsx = require('node:fs');
|
||||
const pathx = require('node:path');
|
||||
const { createTempDir: mkTmp4717, cleanup: cleanup4717 } = require('./helpers.cjs');
|
||||
let tmpCodexHome;
|
||||
let originalCodexHome;
|
||||
let originalGsdRuntime;
|
||||
let originalMsdRuntime;
|
||||
|
||||
let originalGsdHome;
|
||||
let originalMsdHome;
|
||||
let originalHome;
|
||||
let originalUserProfile;
|
||||
|
||||
beforeEach(() => {
|
||||
originalCodexHome = process.env.CODEX_HOME;
|
||||
originalGsdRuntime = process.env.GSD_RUNTIME;
|
||||
originalGsdHome = process.env.GSD_HOME;
|
||||
originalMsdRuntime = process.env.MSD_RUNTIME;
|
||||
originalMsdHome = process.env.MSD_HOME;
|
||||
originalHome = process.env.HOME;
|
||||
originalUserProfile = process.env.USERPROFILE;
|
||||
delete process.env.GSD_RUNTIME;
|
||||
tmpCodexHome = mkTmp4717('gsd-4717-');
|
||||
delete process.env.MSD_RUNTIME;
|
||||
tmpCodexHome = mkTmp4717('msd-4717-');
|
||||
process.env.CODEX_HOME = tmpCodexHome;
|
||||
// Isolate the SHARED defaults file too: loadConfigResolved's global-defaults
|
||||
// branch reads ~/.gsd/defaults.json from GSD_HOME || homedir, and a real
|
||||
// branch reads ~/.msd/defaults.json from MSD_HOME || homedir, and a real
|
||||
// machine's stamped defaults would bleed into these rows (#4717 review).
|
||||
process.env.GSD_HOME = tmpCodexHome;
|
||||
process.env.MSD_HOME = tmpCodexHome;
|
||||
process.env.HOME = tmpCodexHome;
|
||||
process.env.USERPROFILE = tmpCodexHome;
|
||||
slash._setInstallRuntimeMarkerForTests('codex');
|
||||
@@ -1741,10 +1741,10 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
slash._resetInstallRuntimeMarkerCacheForTests();
|
||||
if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
|
||||
else process.env.CODEX_HOME = originalCodexHome;
|
||||
if (originalGsdRuntime === undefined) delete process.env.GSD_RUNTIME;
|
||||
else process.env.GSD_RUNTIME = originalGsdRuntime;
|
||||
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
||||
else process.env.GSD_HOME = originalGsdHome;
|
||||
if (originalMsdRuntime === undefined) delete process.env.MSD_RUNTIME;
|
||||
else process.env.MSD_RUNTIME = originalMsdRuntime;
|
||||
if (originalMsdHome === undefined) delete process.env.MSD_HOME;
|
||||
else process.env.MSD_HOME = originalMsdHome;
|
||||
if (originalHome === undefined) delete process.env.HOME;
|
||||
else process.env.HOME = originalHome;
|
||||
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
|
||||
@@ -1753,17 +1753,17 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
});
|
||||
|
||||
test('an empty config.runtime is filled from the install marker (#4717)', (t) => {
|
||||
const projDir = mkTmp4717('gsd-4717-proj-');
|
||||
const projDir = mkTmp4717('msd-4717-proj-');
|
||||
t.after(() => cleanup4717(projDir));
|
||||
const resolved = loadConfigResolved(projDir, { persist: false });
|
||||
assert.equal(resolved.config.runtime, 'codex',
|
||||
'the marker-owned runtime fills an empty config.runtime (copy-on-write)');
|
||||
});
|
||||
|
||||
test('GSD_RUNTIME outranks the marker in the identity fill (#4717)', (t) => {
|
||||
const projDir = mkTmp4717('gsd-4717-proj-');
|
||||
test('MSD_RUNTIME outranks the marker in the identity fill (#4717)', (t) => {
|
||||
const projDir = mkTmp4717('msd-4717-proj-');
|
||||
t.after(() => cleanup4717(projDir));
|
||||
process.env.GSD_RUNTIME = 'kimi';
|
||||
process.env.MSD_RUNTIME = 'kimi';
|
||||
const resolved = loadConfigResolved(projDir, { persist: false });
|
||||
assert.equal(resolved.config.runtime, 'kimi');
|
||||
});
|
||||
@@ -1778,17 +1778,17 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
|
||||
test('#4717 stamped-defaults leg: a shared defaults runtime does not leak past THIS install\'s marker', (t) => {
|
||||
// The issue's second failure shape: the first non-Claude install stamped
|
||||
// `runtime` into the SHARED ~/.gsd/defaults.json; a Claude install on the
|
||||
// `runtime` into the SHARED ~/.msd/defaults.json; a Claude install on the
|
||||
// same machine must not inherit that identity. Branch D forwards the
|
||||
// stamped value; the fill corrects it to the marker's own.
|
||||
fsx.mkdirSync(pathx.join(tmpCodexHome, '.gsd'), { recursive: true });
|
||||
fsx.mkdirSync(pathx.join(tmpCodexHome, '.msd'), { recursive: true });
|
||||
fsx.writeFileSync(
|
||||
pathx.join(tmpCodexHome, '.gsd', 'defaults.json'),
|
||||
pathx.join(tmpCodexHome, '.msd', 'defaults.json'),
|
||||
JSON.stringify({ runtime: 'codex' }),
|
||||
);
|
||||
slash._setInstallRuntimeMarkerForTests('claude');
|
||||
|
||||
const projDir = bareProjDir(t, 'gsd-4717-proj-stamped-');
|
||||
const projDir = bareProjDir(t, 'msd-4717-proj-stamped-');
|
||||
|
||||
const resolved = loadConfigResolved(projDir);
|
||||
assert.equal(resolved.source, 'global-defaults', 'fixture: the shared-defaults branch must fire');
|
||||
@@ -1800,14 +1800,14 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
});
|
||||
|
||||
test('#4717 stamped-defaults leg: with no marker of its own, the stamped value still stands (status quo preserved)', (t) => {
|
||||
fsx.mkdirSync(pathx.join(tmpCodexHome, '.gsd'), { recursive: true });
|
||||
fsx.mkdirSync(pathx.join(tmpCodexHome, '.msd'), { recursive: true });
|
||||
fsx.writeFileSync(
|
||||
pathx.join(tmpCodexHome, '.gsd', 'defaults.json'),
|
||||
pathx.join(tmpCodexHome, '.msd', 'defaults.json'),
|
||||
JSON.stringify({ runtime: 'codex' }),
|
||||
);
|
||||
slash._setInstallRuntimeMarkerForTests(null);
|
||||
|
||||
const projDir = bareProjDir(t, 'gsd-4717-proj-stamped-nomarker-');
|
||||
const projDir = bareProjDir(t, 'msd-4717-proj-stamped-nomarker-');
|
||||
|
||||
const resolved = loadConfigResolved(projDir);
|
||||
assert.equal(resolved.config.runtime, 'codex', 'no own identity — the stamped value is all we know');
|
||||
@@ -1816,7 +1816,7 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
test('#4717 fail-safe: a garbage marker does not fill the runtime (#4717 review)', (_t) => {
|
||||
slash._setInstallRuntimeMarkerForTests(' not-a-runtime ');
|
||||
|
||||
const projDir = bareProjDir(_t, 'gsd-4717-proj-garbage-');
|
||||
const projDir = bareProjDir(_t, 'msd-4717-proj-garbage-');
|
||||
|
||||
const resolved = loadConfigResolved(projDir);
|
||||
assert.equal(
|
||||
@@ -1827,7 +1827,7 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
});
|
||||
|
||||
test('an explicit config.runtime is preserved — the fill never overrides it (#4717)', (t) => {
|
||||
const projDir = mkTmp4717('gsd-4717-proj-');
|
||||
const projDir = mkTmp4717('msd-4717-proj-');
|
||||
t.after(() => cleanup4717(projDir));
|
||||
fsx.mkdirSync(pathx.join(projDir, '.planning'), { recursive: true });
|
||||
fsx.writeFileSync(
|
||||
@@ -1840,7 +1840,7 @@ describe('loadConfigResolved — runtime identity fill (#4717)', () => {
|
||||
});
|
||||
|
||||
test('copy-on-write: the shared builtin-defaults object is never mutated (#4717)', (t) => {
|
||||
const projDir = mkTmp4717('gsd-4717-proj-');
|
||||
const projDir = mkTmp4717('msd-4717-proj-');
|
||||
t.after(() => cleanup4717(projDir));
|
||||
const resolved = loadConfigResolved(projDir, { persist: false });
|
||||
assert.equal(resolved.config.runtime, 'codex');
|
||||
|
||||
Reference in New Issue
Block a user