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.
69 lines
3.4 KiB
JavaScript
69 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
// Regression guard for #2018 — applySurface over an empty/unresolvable manifest
|
|
// silently deleted every msd-* agent because the agent-prune loop in _syncMsdDir
|
|
// didn't check manifest membership (unlike pruneSkillDirs which conservatively
|
|
// preserves everything when the manifest is empty). The fix skips the agent-prune
|
|
// loop when the manifest is empty/absent so agents are never bulk-deleted.
|
|
|
|
const { describe, test, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const { _syncMsdDir } = require('../msd-core/bin/lib/surface.cjs');
|
|
const { cleanup } = require('./helpers.cjs');
|
|
|
|
function tmp() { return fs.mkdtempSync(path.join(os.tmpdir(), 'msd-surface-empty-')); }
|
|
|
|
describe('#2018 — empty manifest must not delete msd-* agents', () => {
|
|
let dest, staged;
|
|
beforeEach(() => {
|
|
dest = tmp(); staged = tmp();
|
|
// Simulate existing agents in dest (what a real install has)
|
|
for (const name of ['msd-executor.md', 'msd-planner.md', 'msd-verifier.md']) {
|
|
fs.writeFileSync(path.join(dest, name), `# ${name}\n`);
|
|
}
|
|
// Staged dir is EMPTY (unresolvable manifest → nothing staged)
|
|
});
|
|
afterEach(() => { cleanup(dest); cleanup(staged); });
|
|
|
|
test('empty manifest → agents preserved (not deleted)', () => {
|
|
_syncMsdDir(staged, dest, 'agents', new Map());
|
|
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('msd-'));
|
|
assert.deepEqual(remaining.sort(), ['msd-executor.md', 'msd-planner.md', 'msd-verifier.md'],
|
|
'all msd-* agents must survive an empty manifest');
|
|
});
|
|
|
|
test('undefined manifest → agents preserved', () => {
|
|
_syncMsdDir(staged, dest, 'agents', undefined);
|
|
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('msd-'));
|
|
assert.ok(remaining.length === 3, 'all agents preserved with undefined manifest');
|
|
});
|
|
|
|
test('non-empty manifest + empty staged → agents still preserved (staged set empty but manifest exists)', () => {
|
|
// A populated manifest (even with a different set) is trusted; but staged is empty.
|
|
// The manifest gate only fires on EMPTY manifest. With a non-empty manifest but
|
|
// empty staged, the prune loop SHOULD run (agents not in staged get pruned).
|
|
// This test documents the boundary: non-empty manifest → prune happens.
|
|
const manifest = new Map([['msd-old-agent', []]]);
|
|
_syncMsdDir(staged, dest, 'agents', manifest);
|
|
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('msd-'));
|
|
// With a non-empty manifest, staged is empty → all agents pruned (this is the
|
|
// existing behavior for a genuine "all agents were un-surfaced" scenario, NOT
|
|
// the empty-manifest bug).
|
|
assert.deepEqual(remaining, [], 'non-empty manifest + empty staged → agents pruned (expected behavior)');
|
|
});
|
|
|
|
test('empty manifest but new agents staged → new agents added, existing preserved', () => {
|
|
// Stage a new agent
|
|
fs.writeFileSync(path.join(staged, 'msd-new-agent.md'), '# New\n');
|
|
_syncMsdDir(staged, dest, 'agents', new Map());
|
|
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('msd-'));
|
|
assert.ok(remaining.includes('msd-new-agent.md'), 'new staged agent must be copied');
|
|
assert.ok(remaining.includes('msd-executor.md'), 'existing agent must be preserved');
|
|
assert.ok(remaining.length === 4, '3 existing + 1 new = 4 agents');
|
|
});
|
|
});
|