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,9 +1,9 @@
/**
* Runtime surface module — ADR-0011 Phase 2 (Option B).
*
* Manages the runtime enable/disable surface state (the `.gsd-surface.json` marker in
* Manages the runtime enable/disable surface state (the `.msd-surface.json` marker in
* each runtime's config dir root (e.g., ~/.claude)) independently of the install-time profile marker
* (`.gsd-profile`). Runtime config locations are resolved by callers.
* (`.msd-profile`). Runtime config locations are resolved by callers.
*
* Effective skill set = base profile ∪ explicitAdds − disabledClusters − explicitRemoves,
* then transitively closed via the manifest.
@@ -72,8 +72,8 @@ import runtimeArtifactInstallPlan = require('./runtime-artifact-install-plan.cjs
import retiredArtifactCleanup = require('./retired-artifact-cleanup.cjs');
const { assertDestWithinConfigHome } = runtimeArtifactInstallPlan;
const SURFACE_FILE_NAME = '.gsd-surface.json';
const INSTALL_MANIFEST_FILE_NAME = 'gsd-file-manifest.json';
const SURFACE_FILE_NAME = '.msd-surface.json';
const INSTALL_MANIFEST_FILE_NAME = 'msd-file-manifest.json';
/** Read only the installer-owned relative-include style, failing closed. */
function readRelativeIncludePrefix(runtimeConfigDir: string): string | undefined {
@@ -206,7 +206,7 @@ function clustersToSkills(clusterNames: string[], clusterMap: ClusterMap | Recor
* Supports:
* - canonical Map<string, string[]>
* - legacy plain object map: { [stem]: string[] }
* - parsed gsd-file-manifest.json object ({ files: { ... } }) by rebuilding
* - parsed msd-file-manifest.json object ({ files: { ... } }) by rebuilding
* the dependency manifest from the install source tree
*/
function normalizeSkillManifest(runtimeConfigDir: string, manifest: Map<string, string[]> | object | null | undefined): Map<string, string[]> {
@@ -223,7 +223,7 @@ function normalizeSkillManifest(runtimeConfigDir: string, manifest: Map<string,
return new Map(arrayEntries.map(([stem, deps]) => [stem, deps]));
}
// Parsed gsd-file-manifest.json shape: rebuild the dependency map from source.
// Parsed msd-file-manifest.json shape: rebuild the dependency map from source.
const manifestObj = manifest as Record<string, unknown>;
if (manifestObj['files'] && typeof manifestObj['files'] === 'object') {
const srcCommandsDir = findInstallSourceRoot(runtimeConfigDir);
@@ -285,7 +285,7 @@ function resolveSurface(runtimeConfigDir: string, manifest: Map<string, string[]
const skillManifest = normalizeSkillManifest(runtimeConfigDir, manifest);
const surface = surfaceOverride === undefined ? readSurface(runtimeConfigDir) : surfaceOverride;
// Determine base profile name: from surface state or from .gsd-profile marker
// Determine base profile name: from surface state or from .msd-profile marker
const baseProfileName = (surface && surface.baseProfile)
? surface.baseProfile
: (readActiveProfile(runtimeConfigDir) || 'full');
@@ -306,7 +306,7 @@ function resolveSurface(runtimeConfigDir: string, manifest: Map<string, string[]
if (!key.startsWith('_calls_agents_')) skills.add(key);
}
// Issue #2045 (DEFECT 1): third-party capability skills live at
// ~/.gsd/capabilities/<id>/skills/<stem>/SKILL.md — NOT in the runtime skills
// ~/.msd/capabilities/<id>/skills/<stem>/SKILL.md — NOT in the runtime skills
// dir → never in skillManifest → never in the Set → surfaced:false. The
// overlay-aware registry's `capabilityClusters` already covers accepted
// overlay caps (composed by loadRegistry({includeInstalled})), so union its
@@ -390,7 +390,7 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
}
// #3712: the dest selection below prefers `kind.home` over layout.configDir and
// then hands it to the destructive _syncGsdDir, so surface apply is a third
// then hands it to the destructive _syncMsdDir, so surface apply is a third
// escape route into the developer's real home alongside install/uninstall.
testHomeGuard.assertTestHomeSandboxed('applySurface', layout.runtime, layout.kinds, {
os: deps.os, env: deps.env,
@@ -422,7 +422,7 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
const _isOpencode = layout.runtime === 'opencode';
const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
// #4377: style is an install-time fact, not a process environment setting.
// A later gsd-tools surface apply runs in another process, so it must reuse
// A later msd-tools surface apply runs in another process, so it must reuse
// the persisted, root-relative prefix the installer actually emitted.
const _relativeIncludePrefix = readRelativeIncludePrefix(runtimeConfigDir);
const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir, projectRelative: !_isGlobal && typeof _relativeIncludePrefix === 'string', projectRelativePath: _relativeIncludePrefix, localDirName: runtimeArtifactConversion._localIncludeDirName(layout.runtime) });
@@ -500,7 +500,7 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
// and the persisted surface state untouched.
retiredArtifactCleanup.pruneRetiredRuntimeArtifacts(layout.runtime, layout.configDir);
for (const item of stagedKinds) {
_syncGsdDir(item.staged, item.dest, item.kind, skillManifest, layout.runtime);
_syncMsdDir(item.staged, item.dest, item.kind, skillManifest, layout.runtime);
}
if (hasCandidateState) {
@@ -519,21 +519,21 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
}
/**
* Prune GSD-managed skill directories from a skills directory.
* Prune MSD-managed skill directories from a skills directory.
*
* Removes every directory in `skillsDir` that is GSD-owned but NOT listed
* in `retainedNames`. User-owned dirs (not matching the GSD ownership criteria)
* Removes every directory in `skillsDir` that is MSD-owned but NOT listed
* in `retainedNames`. User-owned dirs (not matching the MSD ownership criteria)
* are always preserved.
*
* Ownership criteria:
* - Non-empty prefix (e.g. 'gsd-'): dir name starts with that prefix AND
* - Non-empty prefix (e.g. 'msd-'): dir name starts with that prefix AND
* EITHER appears in the manifest (first-party membership) OR carries the
* persisted `CAPABILITY_SKILL_MARKER` file (#2322 HIGH-3: a third-party
* capability skill, self-certifying and independent of current registry
* state — so an uninstalled/unsurfaced capability's stale skill is still
* prunable even though it no longer appears in any registry view). Dirs
* that match the prefix but satisfy NEITHER are treated as user-owned and
* preserved — this prevents data loss for user-created gsd-* directories.
* preserved — this prevents data loss for user-created msd-* directories.
* A warning is written to stderr when such a dir is encountered.
* - Empty prefix (Hermes): dir name appears as a canonical skill stem in the
* manifest. User dirs not in the manifest are preserved. (Hermes does not
@@ -542,12 +542,12 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
* - Empty prefix without manifest, or manifest not a Map: conservative; no
* dirs are removed.
*
* This is the single point of truth for skill-dir pruning. Both _syncGsdDir
* This is the single point of truth for skill-dir pruning. Both _syncMsdDir
* (surface apply) and callers that need stand-alone pruning use this function.
*
* @param skillsDir directory that contains the gsd-STEM sub-dirs
* @param retainedNames set of directory names to keep (e.g. 'gsd-help')
* @param prefix GSD dir prefix, e.g. 'gsd-' (or '' for Hermes)
* @param skillsDir directory that contains the msd-STEM sub-dirs
* @param retainedNames set of directory names to keep (e.g. 'msd-help')
* @param prefix MSD dir prefix, e.g. 'msd-' (or '' for Hermes)
* @param manifest optional; required for Hermes empty-prefix case
* and for manifest-membership gate in prefixed case.
* Must be a Map; any other type is treated as missing.
@@ -569,7 +569,7 @@ function pruneSkillDirs(skillsDir: string, retainedNames: Set<string>, prefix: s
const entryPath = path.join(skillsDir, entry);
if (!fs.statSync(entryPath).isDirectory()) continue;
let isGsdOwned: boolean;
let isMsdOwned: boolean;
if (prefix !== '') {
if (!entry.startsWith(prefix)) {
// Does not match prefix at all — user-owned, preserve.
@@ -577,11 +577,11 @@ function pruneSkillDirs(skillsDir: string, retainedNames: Set<string>, prefix: s
}
// #2322: an entry in THIS apply's retained set is unambiguously wanted —
// check that BEFORE the first-party-manifest-membership gate below. The
// manifest only ever knows gsd-core's own bundled stems; a materialized
// manifest only ever knows msd-core's own bundled stems; a materialized
// third-party capability skill (retained via the resolved profile's
// registry union, #2045/#2322) has no manifest entry at all, so without
// this early check it fell into the "unknown, preserve with warning"
// branch on EVERY apply — misreporting a live, GSD-managed capability
// branch on EVERY apply — misreporting a live, MSD-managed capability
// skill as "user-owned or unknown" noise. This does not change any
// deletion outcome (a retained entry was always preserved — see the
// `retainedNames.has(entry)` check further below); it only short-
@@ -589,14 +589,14 @@ function pruneSkillDirs(skillsDir: string, retainedNames: Set<string>, prefix: s
// this apply, are wanted.
if (retainedNames.has(entry)) continue;
// Finding 1 fix: prefix match is necessary but NOT sufficient.
// The dir must also be in the manifest to be considered GSD-owned.
// A user-created gsd-* dir that isn't in the manifest is preserved with a warning.
// The dir must also be in the manifest to be considered MSD-owned.
// A user-created msd-* dir that isn't in the manifest is preserved with a warning.
const stem = entry.slice(prefix.length);
if (canonicalStems && canonicalStems.has(stem)) {
isGsdOwned = true;
isMsdOwned = true;
} else if (fs.existsSync(path.join(entryPath, CAPABILITY_SKILL_MARKER))) {
// #2322 HIGH-3: not a first-party stem, but self-certified as a
// GSD-managed THIRD-PARTY capability skill via the persisted marker
// MSD-managed THIRD-PARTY capability skill via the persisted marker
// (written by install-profiles.cts stageSkillsForRuntimeAsSkills at
// stage time). Without this, an orphaned capability skill — its
// owning capability uninstalled/unsurfaced and no longer appearing in
@@ -606,27 +606,27 @@ function pruneSkillDirs(skillsDir: string, retainedNames: Set<string>, prefix: s
// instructions from the agent's context. The marker makes ownership
// self-certifying at prune time, independent of current registry
// state (or even of whether a manifest was supplied at all).
isGsdOwned = true;
isMsdOwned = true;
} else if (!canonicalStems) {
// No manifest available and no capability marker: cannot confirm
// ownership — preserve conservatively (silent).
continue;
} else {
process.stderr.write(
`[gsd] Warning: ${entry} matches GSD prefix '${prefix}' but is not in the manifest — preserving (user-owned or unknown)\n`
`[msd] Warning: ${entry} matches MSD prefix '${prefix}' but is not in the manifest — preserving (user-owned or unknown)\n`
);
continue;
}
} else if (canonicalStems) {
// Hermes: GSD-owned iff the directory name appears in the canonical manifest.
isGsdOwned = canonicalStems.has(entry);
// Hermes: MSD-owned iff the directory name appears in the canonical manifest.
isMsdOwned = canonicalStems.has(entry);
} else {
// No manifest available: be conservative, don't remove anything.
continue;
}
if (!isGsdOwned) continue; // Hermes path only: preserve user-owned dirs not in manifest
if (retainedNames.has(entry)) continue; // GSD-owned and in retain set
if (!isMsdOwned) continue; // Hermes path only: preserve user-owned dirs not in manifest
if (retainedNames.has(entry)) continue; // MSD-owned and in retain set
try {
fs.rmSync(entryPath, { recursive: true, force: true });
} catch (err) {
@@ -639,22 +639,22 @@ function pruneSkillDirs(skillsDir: string, retainedNames: Set<string>, prefix: s
* Sync destination directory from staged source.
*
* For 'commands' kind: iterate *.md files in destDir, remove if not in staged set.
* For 'agents' kind: same, but only remove files starting with 'gsd-' prefix.
* For 'agents' kind: same, but only remove files starting with 'msd-' prefix.
* For 'skills' kind: iterate directories in destDir matching kind.prefix; add missing
* by copying recursively; remove dirs not in staged set. Preserves dirs not matching
* the prefix (user-owned skills). Pruning is delegated to pruneSkillDirs().
*
* For Hermes (empty prefix): uses manifest membership to discriminate GSD-owned vs
* user-owned dirs. GSD-owned = stem in manifest; removal targets = in manifest AND
* For Hermes (empty prefix): uses manifest membership to discriminate MSD-owned vs
* user-owned dirs. MSD-owned = stem in manifest; removal targets = in manifest AND
* not in staged set. User-owned (not in manifest) are always preserved.
*/
function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | string, manifest?: Map<string, string[]>, runtime?: string): void {
function _syncMsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | string, manifest?: Map<string, string[]>, runtime?: string): void {
if (!fs.existsSync(stagedDir)) return;
fs.mkdirSync(destDir, { recursive: true });
// Normalize: allow legacy string context for backward-compat with internal callers
const kindName = (typeof kind === 'string') ? kind : kind.kind;
const kindPrefix = (typeof kind === 'object' && kind !== null) ? kind.prefix : 'gsd-';
const kindPrefix = (typeof kind === 'object' && kind !== null) ? kind.prefix : 'msd-';
// #1575 / #2103: agent files are renamed .md -> <agentFileExtension> at copy
// time when the runtime's descriptor declares hostBehaviors.agentFileExtension
@@ -666,23 +666,23 @@ function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | st
const isRenamedAgents = !!_agentExt && kindName === 'agents';
if (kindName === 'kimi-agents') {
// #4211: Kimi's managed tree is `gsd.yaml` + `gsd.md` + `subagents/gsd-*.{yaml,md}`
// #4211: Kimi's managed tree is `msd.yaml` + `msd.md` + `subagents/msd-*.{yaml,md}`
// (runtime-artifact-layout.cts kimiAgentsKind), and install copies it
// RECURSIVELY (_copyStaged in src/install-engine.cts). Surface apply fell
// through to the flat command/agent branch below, which reads only `*.md`
// at the top level: the YAML half and the whole subagents/ subtree were
// dropped, and `gsd.md` was written as `gsdgsd.md` (the flat branch
// dropped, and `msd.md` was written as `msdmsd.md` (the flat branch
// re-applies kind.prefix to a name that already carries it). A surface
// change could therefore corrupt Kimi's installed artifacts while still
// reporting success.
fs.cpSync(stagedDir, destDir, { recursive: true });
// Prune GSD-owned files the new surface no longer stages, with exactly the
// ownership rule install's _removeGsdEntries applies to this kind: the two
// root files, and `gsd-`-prefixed .yaml/.md under subagents/. Everything
// Prune MSD-owned files the new surface no longer stages, with exactly the
// ownership rule install's _removeMsdEntries applies to this kind: the two
// root files, and `msd-`-prefixed .yaml/.md under subagents/. Everything
// else in the directory is user-owned and is preserved.
const _rootStaged = new Set(fs.readdirSync(stagedDir));
for (const fileName of ['gsd.yaml', 'gsd.md']) {
for (const fileName of ['msd.yaml', 'msd.md']) {
if (!_rootStaged.has(fileName)) {
try { fs.rmSync(path.join(destDir, fileName), { force: true }); } catch { /* ignore */ }
}
@@ -695,7 +695,7 @@ function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | st
if (fs.existsSync(_destSubagentsDir)) {
for (const entry of fs.readdirSync(_destSubagentsDir, { withFileTypes: true })) {
if (!entry.isFile()) continue;
if (!entry.name.startsWith('gsd-')) continue;
if (!entry.name.startsWith('msd-')) continue;
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md')) continue;
if (_stagedSubagents.has(entry.name)) continue;
try { fs.rmSync(path.join(_destSubagentsDir, entry.name), { force: true }); } catch { /* ignore */ }
@@ -719,16 +719,16 @@ function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | st
fs.cpSync(path.join(stagedDir, dirName), destSubDir, { recursive: true });
}
// Prune GSD-owned dirs that are no longer in the staged set.
// Prune MSD-owned dirs that are no longer in the staged set.
// pruneSkillDirs() is the single point of truth for this logic.
pruneSkillDirs(destDir, stagedDirs, kindPrefix, manifest);
} else {
// commands / agents kind: mirror installRuntimeArtifacts (_copyStaged /
// _removeGsdEntries in bin/install.js) so surface produces the SAME files as a
// _removeMsdEntries in bin/install.js) so surface produces the SAME files as a
// fresh install (#816). Flat command dirs (opencode/cursor/augment/kilo) take
// the gsd- prefix on copy; namespaced command dirs (commands/gsd) and agents
// the msd- prefix on copy; namespaced command dirs (commands/msd) and agents
// keep their staged names. Copying staged names verbatim previously diverged
// from install and orphaned the installed gsd-*.md files, and the unscoped
// from install and orphaned the installed msd-*.md files, and the unscoped
// prune deleted user-owned command files.
//
// Single source of truth: runtimeArtifactLayout.isNamespacedByDir (#2871
@@ -749,15 +749,15 @@ function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | st
stagedDestNames.add(destName);
}
// Prune stale GSD-owned files not in the staged set, preserving user-owned files
// (mirrors install's prefix-scoped _removeGsdEntries):
// - agents: only gsd-* are GSD-owned (copilot: gsd-*.agent.md)
// - flat command dirs: only `${kindPrefix}`-prefixed are GSD-owned
// - namespaced command dirs: the whole dir is GSD-owned
// Prune stale MSD-owned files not in the staged set, preserving user-owned files
// (mirrors install's prefix-scoped _removeMsdEntries):
// - agents: only msd-* are MSD-owned (copilot: msd-*.agent.md)
// - flat command dirs: only `${kindPrefix}`-prefixed are MSD-owned
// - namespaced command dirs: the whole dir is MSD-owned
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 === 'agents' && !file.startsWith('msd-')) continue;
if (kindName === 'commands' && !namespacedByDir && kindPrefix && !file.startsWith(kindPrefix)) continue;
if (!stagedDestNames.has(file)) {
try { fs.unlinkSync(path.join(destDir, file)); } catch { /* ignore */ }
@@ -821,5 +821,5 @@ export = {
listSurface,
// Exported for testing and for callers that need stand-alone pruning
pruneSkillDirs,
_syncGsdDir,
_syncMsdDir,
};