* fix(#2018): applySurface empty manifest no longer deletes gsd-* agents The agent-prune loop in _syncGsdDir deleted any gsd-*.md not in the staged set. When the manifest resolved empty (null/non-object, no entries, no files key, or unresolvable install source root), the staged set was empty → every gsd-* agent was deleted. Skills were guarded by pruneSkillDirs' manifest- membership check; agents had no equivalent. - src/surface.cts: skip the agent-prune loop when the manifest is empty/absent (copy still runs — genuinely new agents are added). - tests/surface-empty-manifest-agents.test.cjs: boundary matrix — empty manifest preserves agents (Map() + undefined); non-empty manifest + empty staged still prunes (boundary); empty manifest + new staged copies new + preserves existing. Closes #2018 * docs(#2018): backfill changeset pr 2031
This commit is contained in:
@@ -509,11 +509,20 @@ function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | st
|
||||
// - agents: only gsd-* are GSD-owned
|
||||
// - flat command dirs: only `${kindPrefix}`-prefixed are GSD-owned
|
||||
// - namespaced command dirs: the whole dir is GSD-owned
|
||||
for (const file of fs.readdirSync(destDir).filter(f => f.endsWith('.md'))) {
|
||||
if (kindName === 'agents' && !file.startsWith('gsd-')) continue;
|
||||
if (kindName === 'commands' && !namespacedByDir && kindPrefix && !file.startsWith(kindPrefix)) continue;
|
||||
if (!stagedDestNames.has(file)) {
|
||||
try { fs.unlinkSync(path.join(destDir, file)); } catch { /* ignore */ }
|
||||
//
|
||||
// Manifest gate (#2018): when the manifest is empty/absent (e.g. an unresolvable
|
||||
// install source root yields an empty staged dir), the staged set is untrustworthy.
|
||||
// Skills are guarded by pruneSkillDirs' manifest-membership check; agents must be
|
||||
// guarded here — skip the prune loop entirely so an empty manifest never deletes
|
||||
// every gsd-* agent. Copying (above) still runs so genuinely new agents are added.
|
||||
const shouldPruneAgents = !(kindName === 'agents' && (!manifest || manifest.size === 0));
|
||||
if (shouldPruneAgents) {
|
||||
for (const file of fs.readdirSync(destDir).filter(f => f.endsWith('.md'))) {
|
||||
if (kindName === 'agents' && !file.startsWith('gsd-')) continue;
|
||||
if (kindName === 'commands' && !namespacedByDir && kindPrefix && !file.startsWith(kindPrefix)) continue;
|
||||
if (!stagedDestNames.has(file)) {
|
||||
try { fs.unlinkSync(path.join(destDir, file)); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user