refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity
Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes, cline, codebuddy and pi end to end: capability descriptors, installer branches and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters, hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi migrations, Kimi payload normalization in the hook guards, dead hostBehaviors vocabulary, launcher home probes, fixtures, runtime-specific tests and the prose that presented them as supported. Installer output for the six kept runtimes is byte-identical to before the prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and read-injection-scanner are left in place pending a decision.
This commit is contained in:
@@ -191,7 +191,7 @@ function provisionRuntimeSurfaceCorpus(
|
||||
if (isGlobalScope(scope as InstallScope)) {
|
||||
for (const kind of layout.kinds) {
|
||||
if (kind.kind === 'commands' || kind.kind === 'skills') required.add('commands');
|
||||
if (kind.kind === 'agents' || kind.kind === 'kimi-agents') required.add('agents');
|
||||
if (kind.kind === 'agents') required.add('agents');
|
||||
}
|
||||
}
|
||||
if (required.size === 0) return;
|
||||
@@ -283,7 +283,6 @@ function applyOpencodeFamilyPathPrefix(content: string, runtime: string, pathPre
|
||||
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
||||
content = content.replace(/\.\/\.claude\//g, `./${getDirName(runtime)}/`);
|
||||
content = content.replace(/~\/\.opencode\//g, pathPrefix);
|
||||
content = content.replace(/~\/\.kilo\//g, pathPrefix);
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -297,25 +296,15 @@ function convertClaudeCommandToOpencodeSkill(content: string, skillName: string)
|
||||
return (runtimeArtifactConversion as any).convertClaudeCommandToOpencodeSkill(content, skillName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Claude command (.md) to a Kilo skill (SKILL.md).
|
||||
* Thin wrapper over the shared OpenCode-family writer (Kilo shares the schema).
|
||||
*/
|
||||
function convertClaudeCommandToKiloSkill(content: string, skillName: string): string {
|
||||
return (runtimeArtifactConversion as any).convertClaudeCommandToKiloSkill(content, skillName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter-name registry for the OpenCode-family combined skills installer
|
||||
* (ADR-1239 / #2093). Maps the `converter` string declared on each runtime's
|
||||
* artifactLayout skills-kind descriptor (capabilities/<runtime>/capability.json)
|
||||
* to the actual conversion function, so `installOpencodeFamilySkills` dispatches
|
||||
* off the descriptor instead of a `frontmatterDialect === 'kilo'` runtime check.
|
||||
* off the descriptor instead of a `frontmatterDialect` runtime check.
|
||||
*/
|
||||
const SKILLS_CONVERTER_REGISTRY: Record<string, (content: string, skillName: string) => string> = {
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
convertClaudeCommandToKimiCodeSkill: runtimeArtifactConversion.convertClaudeCommandToKimiCodeSkill,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -558,7 +547,7 @@ function _tryResolveUserArtifactStagingRoot(configDir: string): string | null {
|
||||
* Migrate a legacy dev-preferences.md (saved from commands/msd/) into the
|
||||
* runtime-aware SKILL.md location used by the writer after #2973.
|
||||
*
|
||||
* For runtimes with a nested skills layout (e.g. Hermes: skills/msd/<stem>/),
|
||||
* For runtimes with a nested skills layout (skills/msd/<stem>/),
|
||||
* the target is <configDir>/skills/msd/dev-preferences/SKILL.md.
|
||||
* For runtimes with a flat skills layout (prefix='msd-'), the target is
|
||||
* <configDir>/skills/msd-dev-preferences/SKILL.md.
|
||||
@@ -573,7 +562,7 @@ function _tryResolveUserArtifactStagingRoot(configDir: string): string | null {
|
||||
* user-artifact-staging.cts staged batch's disk contents (#2875) — every
|
||||
* call site reads this back AFTER its own wipe, never held in memory
|
||||
* across it.
|
||||
* @param runtime - canonical runtime ID (e.g. 'hermes', 'qwen', 'claude')
|
||||
* @param runtime - canonical runtime ID (e.g. 'claude')
|
||||
* @param scope - install scope
|
||||
* @returns true if a file was migrated, false otherwise
|
||||
*/
|
||||
@@ -618,7 +607,7 @@ function _resolveDevPreferencesSkillTarget(targetDir: string, runtime?: string,
|
||||
if (runtime) {
|
||||
const layout: any = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, targetDir, scope as any);
|
||||
const skillsKindEntry = layout.kinds.find((k: any) => k.kind === 'skills');
|
||||
if (!skillsKindEntry) return null; // runtime has no skills layout at this scope (e.g. cline local)
|
||||
if (!skillsKindEntry) return null; // runtime has no skills layout at this scope
|
||||
const stemName = skillsKindEntry.prefix === '' ? 'dev-preferences' : 'msd-dev-preferences';
|
||||
// #2911: same destination-root defect as _copyStaged/applySurface — honor
|
||||
// skillsKindEntry.home as a FALLBACK-preferred override (e.g. Codex skills
|
||||
@@ -652,7 +641,7 @@ function migrateLegacyDevPreferencesToSkill(
|
||||
): boolean {
|
||||
if (!saved || !saved.has('dev-preferences.md')) return false;
|
||||
const target = _resolveDevPreferencesSkillTarget(targetDir, runtime, scope);
|
||||
if (!target) return false; // runtime has no skills layout at this scope (e.g. cline local)
|
||||
if (!target) return false; // runtime has no skills layout at this scope
|
||||
// #3712 — the SIXTH writer that resolves a skills-kind `home` override.
|
||||
// Exported and directly callable, and `_runLegacyInstallMigrations` runs it
|
||||
// BEFORE installRuntimeArtifacts' own assertion, so a future runtime pairing a
|
||||
@@ -728,7 +717,6 @@ function migrateLegacyDevPreferencesToSkill(
|
||||
* encodes the MSD namespace as its last segment (e.g. `commands/msd`), in
|
||||
* which case write as `${stem}.md` (directory IS the namespace).
|
||||
* - agents: write as-is (files already carry their own `msd-` prefix).
|
||||
* For kimi-agents kind: recursively copy generated YAML/prompt files.
|
||||
*/
|
||||
function _copyStaged(stagedDir: string, destDir: string, kind: any, configDir: string, runtime?: string): void {
|
||||
// Defense-in-depth: verify destDir is within the install root even if the
|
||||
@@ -775,11 +763,6 @@ function _copyStaged(stagedDir: string, destDir: string, kind: any, configDir: s
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind.kind === 'kimi-agents') {
|
||||
installFs().cpSync(stagedDir, destDir, { recursive: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// commands or agents
|
||||
const entries = installFs().readdirSync(stagedDir, { withFileTypes: true });
|
||||
// For commands: apply prefix unless the destSubpath's last segment already
|
||||
@@ -806,10 +789,9 @@ function _copyStaged(stagedDir: string, destDir: string, kind: any, configDir: s
|
||||
let destName: string;
|
||||
if (kind.kind === 'agents') {
|
||||
// Agent files already carry the msd- prefix in the source dir.
|
||||
// #2099: descriptor-driven via hostBehaviors.agentFileExtension (was
|
||||
// hardcoded `runtime === 'copilot'`). copilot declares '.agent.md';
|
||||
// every other runtime's descriptor leaves this unset, so destName falls
|
||||
// back to entry.name unchanged (byte-parity, #1575 origin comment).
|
||||
// #2099: descriptor-driven via hostBehaviors.agentFileExtension. No
|
||||
// current runtime declares one, so destName falls back to entry.name
|
||||
// unchanged (byte-parity, #1575 origin comment).
|
||||
const _agentExt = runtime ? _hostBehaviors(runtime).agentFileExtension : undefined;
|
||||
destName = _agentExt
|
||||
? entry.name.replace(/\.md$/, _agentExt)
|
||||
@@ -835,28 +817,13 @@ function _copyStaged(stagedDir: string, destDir: string, kind: any, configDir: s
|
||||
/**
|
||||
* Remove MSD-prefixed entries from destDir matching kind.prefix.
|
||||
* For the prefix='' case: the destSubpath IS the namespace — remove the entire
|
||||
* destDir. (No current runtime uses prefix='' after #947 reversed Hermes; kept
|
||||
* as a defensive guard for future runtimes.)
|
||||
* destDir. (No current runtime uses prefix=''; kept as a defensive guard for
|
||||
* future runtimes.)
|
||||
*/
|
||||
function _removeMsdEntries(destDir: string, kind: any): void {
|
||||
if (!installFs().existsSync(destDir)) return;
|
||||
if (kind.kind === 'kimi-agents') {
|
||||
for (const fileName of ['msd.yaml', 'msd.md']) {
|
||||
installFs().rmSync(path.join(destDir, fileName), { force: true });
|
||||
}
|
||||
const subagentsDir = path.join(destDir, 'subagents');
|
||||
if (installFs().existsSync(subagentsDir)) {
|
||||
for (const entry of installFs().readdirSync(subagentsDir, { withFileTypes: true })) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.startsWith('msd-')) continue;
|
||||
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md')) continue;
|
||||
installFs().rmSync(path.join(subagentsDir, entry.name), { force: true });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (kind.prefix === '') {
|
||||
// Whole-namespace removal (Hermes nested case — destSubpath is skills/msd)
|
||||
// Whole-namespace removal (nested case — destSubpath is skills/msd)
|
||||
// The directory itself is the MSD namespace, so remove it entirely.
|
||||
installFs().rmSync(destDir, { recursive: true, force: true });
|
||||
return;
|
||||
@@ -901,37 +868,6 @@ function _restoreDir(dir: string, snapshot: Map<string, Buffer>): void {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// _removeHermesBareStemDirs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* After the layout-driven install loop writes new msd-<stem>/ dirs to
|
||||
* skills/msd/, remove any pre-existing bare-stem dirs (skills/msd/<stem>/)
|
||||
* that correspond to the newly installed msd-<stem> entries.
|
||||
*
|
||||
* @param nestedMsdDir absolute path to skills/msd/ category dir
|
||||
*/
|
||||
function _removeHermesBareStemDirs(nestedMsdDir: string): void {
|
||||
if (!installFs().existsSync(nestedMsdDir)) return;
|
||||
const entries = installFs().readdirSync(nestedMsdDir, { withFileTypes: true });
|
||||
|
||||
// Collect the set of stems that were installed as msd-<stem>/ this run.
|
||||
const installedStems = new Set<string>();
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name.startsWith('msd-')) {
|
||||
installedStems.add(entry.name.slice('msd-'.length)); // e.g. 'quick', 'dev-preferences'
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any bare <stem>/ dir for which msd-<stem>/ was just installed.
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && !entry.name.startsWith('msd-') && installedStems.has(entry.name)) {
|
||||
installFs().rmSync(path.join(nestedMsdDir, entry.name), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Legacy migration helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -947,10 +883,8 @@ function _removeHermesBareStemDirs(nestedMsdDir: string): void {
|
||||
function _runLegacyInstallMigrations(runtime: string, configDir: string, scope: string = 'global'): void {
|
||||
const legacyCommandsMsd = path.join(configDir, 'commands', 'msd');
|
||||
|
||||
// Claude / Qwen / Hermes: clean up legacy commands/msd/ and preserve dev-preferences
|
||||
// for migration. The actual migration call is deferred to after all layout cleanup so
|
||||
// that for Hermes the flat skills/msd-*/ removal (below) does not delete the freshly
|
||||
// created skills/msd-dev-preferences/ skill dir.
|
||||
// Claude: clean up legacy commands/msd/ and preserve dev-preferences for
|
||||
// migration. The actual migration call is deferred to after all layout cleanup.
|
||||
let stagedLegacyArtifacts: ReturnType<typeof userArtifactStaging.stageUserArtifacts> | null = null;
|
||||
if (_hostBehaviors(runtime).legacyCommandsMsdInstallMigration) {
|
||||
if (installFs().existsSync(legacyCommandsMsd)) {
|
||||
@@ -967,37 +901,16 @@ function _runLegacyInstallMigrations(runtime: string, configDir: string, scope:
|
||||
const stagingRoot = _tryResolveUserArtifactStagingRoot(configDir);
|
||||
if (stagingRoot !== null) {
|
||||
// #2875 (#1874-F19): staged DURABLY to disk before the wipe below, so a
|
||||
// crash anywhere in this function — including the Hermes flat-skills
|
||||
// wipe further down, previously inside the same in-memory-only window
|
||||
// — survives via recoverOrphanedUserArtifacts on the next run.
|
||||
// crash anywhere in this function survives via
|
||||
// recoverOrphanedUserArtifacts on the next run.
|
||||
stagedLegacyArtifacts = userArtifactStaging.stageUserArtifacts(legacyCommandsMsd, ['dev-preferences.md'], stagingRoot);
|
||||
installFs().rmSync(legacyCommandsMsd, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hermes: remove pre-#2841 flat skills/msd-*/ entries that lived alongside
|
||||
// the new skills/msd/ nested layout.
|
||||
if (runtime === 'hermes') {
|
||||
const flatSkillsDir = path.join(configDir, 'skills');
|
||||
if (installFs().existsSync(flatSkillsDir)) {
|
||||
for (const entry of installFs().readdirSync(flatSkillsDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && entry.name.startsWith('msd-')) {
|
||||
installFs().rmSync(path.join(flatSkillsDir, entry.name), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hermes: bare-stem skills/msd/<stem>/ cleanup is deferred to AFTER the
|
||||
// layout-driven install loop in installRuntimeArtifacts, where the exact set
|
||||
// of staged msd-<stem>/ dirs is known. Removing here (before staging) would
|
||||
// require readMsdCommandNames() which misses skills like 'dev-preferences'
|
||||
// that are not in the commands directory. See _removeHermesBareStemDirs().
|
||||
}
|
||||
|
||||
// Migrate dev-preferences.md content → runtime-aware SKILL.md location (#2973).
|
||||
// Done after all layout cleanup so Hermes flat-dir removal does not delete the
|
||||
// newly created skill dir. No-op if skill file already exists.
|
||||
// Done after all layout cleanup. No-op if skill file already exists.
|
||||
if (stagedLegacyArtifacts) {
|
||||
// #2875: read the content back from the DISK-staged copy (fresh, after
|
||||
// every wipe above has already run) rather than an in-memory value held
|
||||
@@ -1095,7 +1008,7 @@ function _runLegacyInstallMigrations(runtime: string, configDir: string, scope:
|
||||
* @returns staged legacy artifacts for post-removal migration, or null
|
||||
*/
|
||||
function _runLegacyUninstallCleanup(runtime: string, configDir: string, scope: string = 'global'): ReturnType<typeof userArtifactStaging.stageUserArtifacts> | null {
|
||||
// commands/msd/ is a legacy location for Qwen, Hermes, and all Claude installs.
|
||||
// commands/msd/ is a legacy location for all Claude installs.
|
||||
// Prior to #1367 fix, Claude-local used commands/msd/<cmd>.md (colon-namespaced).
|
||||
// After #1367, Claude-local uses flat commands/msd-<cmd>.md. The inline uninstall
|
||||
// block (1c) handles removal of flat files; this function handles the legacy
|
||||
@@ -1114,7 +1027,7 @@ function _runLegacyUninstallCleanup(runtime: string, configDir: string, scope: s
|
||||
// module does (ambient default: real fs here, since this call is never
|
||||
// wrapped in withInstallFs).
|
||||
let stagedLegacyArtifacts: ReturnType<typeof userArtifactStaging.stageUserArtifacts> | null = null;
|
||||
// commands/msd/ is a legacy location for Qwen, Hermes, and Claude global.
|
||||
// commands/msd/ is a legacy location for Claude global.
|
||||
// Claude local is intentionally excluded: the inline uninstall block (1c) handles
|
||||
// commands/msd/ for claude local, preserving dev-preferences.md by restoring it
|
||||
// to the same location (#1423). Using migrateLegacyDevPreferencesToSkill here
|
||||
@@ -1145,30 +1058,6 @@ function _runLegacyUninstallCleanup(runtime: string, configDir: string, scope: s
|
||||
}
|
||||
}
|
||||
|
||||
// Hermes: pre-#2841 flat skills/msd-*/ entries
|
||||
if (runtime === 'hermes') {
|
||||
const flatSkillsDir = path.join(configDir, 'skills');
|
||||
if (fs.existsSync(flatSkillsDir)) {
|
||||
for (const entry of fs.readdirSync(flatSkillsDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && entry.name.startsWith('msd-')) {
|
||||
fs.rmSync(path.join(flatSkillsDir, entry.name), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hermes: pre-#947 bare-stem skills/msd/<stem>/ entries (dirs that do NOT
|
||||
// start with 'msd-') — the #3664 layout used prefix='' so MSD-owned skills
|
||||
// had bare names (e.g. skills/msd/help/). These are stale on uninstall.
|
||||
const nestedMsdDirForUninstall = path.join(configDir, 'skills', 'msd');
|
||||
if (fs.existsSync(nestedMsdDirForUninstall)) {
|
||||
for (const entry of fs.readdirSync(nestedMsdDirForUninstall, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && !entry.name.startsWith('msd-')) {
|
||||
fs.rmSync(path.join(nestedMsdDirForUninstall, entry.name), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return staged artifacts so the caller can migrate after layout-driven removal.
|
||||
return stagedLegacyArtifacts;
|
||||
}
|
||||
@@ -1226,14 +1115,14 @@ function installRuntimeArtifacts(
|
||||
// before materializing the current layout (#2644).
|
||||
retiredArtifactCleanup.pruneRetiredRuntimeArtifacts(runtime, configDir);
|
||||
|
||||
// Combined-family runtimes (OpenCode/Kilo, ADR-1239 / #2087): route through
|
||||
// Combined-family runtimes (OpenCode, ADR-1239 / #2087): route through
|
||||
// the dedicated combined commands+skills+plugin orchestrator instead of the
|
||||
// generic layout-driven loop below, mirroring the bespoke install path that
|
||||
// previously lived inline in bin/install.js.
|
||||
const behaviors = _hostBehaviors(runtime);
|
||||
const projectDir = scope === 'global' ? process.cwd() : configDir;
|
||||
if (behaviors.combinedFamilyInstall) {
|
||||
// #2329: combined-family runtimes (OpenCode/Kilo) bypass
|
||||
// #2329: combined-family runtimes (OpenCode) bypass
|
||||
// _runLegacyInstallMigrations below entirely (early return), so their
|
||||
// legacy-directory cleanup needs its own pre-materialization hook here.
|
||||
_migrateLegacyOpencodeCommandDir(runtime, configDir, behaviors);
|
||||
@@ -1377,7 +1266,7 @@ function installRuntimeArtifacts(
|
||||
// then restore after. This preserves user dirs across a wipe-and-replace
|
||||
// install (#2973 / #3664).
|
||||
//
|
||||
// All runtimes (incl. Hermes after #947) use prefix='msd-'.
|
||||
// All runtimes use prefix='msd-'.
|
||||
// _removeMsdEntries removes only msd-* entries; non-msd-* user dirs are
|
||||
// untouched. Preserve the explicit user-owned MSD-prefixed skill
|
||||
// msd-dev-preferences, which MSD does not reinstall from source but must
|
||||
@@ -1427,27 +1316,9 @@ function installRuntimeArtifacts(
|
||||
}
|
||||
}
|
||||
|
||||
// Hermes: after the install loop has written all msd-<stem>/ dirs to
|
||||
// skills/msd/, remove any stale bare-stem dirs (skills/msd/<stem>/) that
|
||||
// correspond to the newly installed msd-<stem> entries. This is the robust
|
||||
// replacement for the readMsdCommandNames()-based pre-install cleanup that
|
||||
// missed skills like 'dev-preferences' (#947 adversarial review).
|
||||
//
|
||||
// We run this AFTER the install loop so the installed set is authoritative:
|
||||
// every msd-<stem>/ present now was written this run (or was there before
|
||||
// with the same prefix). User-owned bare dirs with no msd-<stem> counterpart
|
||||
// are untouched.
|
||||
let hermesBareStemCleanup = false;
|
||||
if (runtime === 'hermes') {
|
||||
const nestedMsdDirForCleanup = path.join(configDir, 'skills', 'msd');
|
||||
_removeHermesBareStemDirs(nestedMsdDirForCleanup);
|
||||
hermesBareStemCleanup = true;
|
||||
}
|
||||
|
||||
// Generic-branch nativePlugin staging (ADR-1239 / #2102 Stage 1): runtimes
|
||||
// outside the OpenCode/Kilo combined-family install (e.g. pi, whose
|
||||
// artifactLayout is empty and which never sets combinedFamilyInstall) still
|
||||
// need their declared hostBehaviors.nativePlugin file copied into configDir.
|
||||
// outside the OpenCode combined-family install that declare a
|
||||
// hostBehaviors.nativePlugin still need that file copied into configDir.
|
||||
// findInstallSourceRoot resolves the repo/package root independent of
|
||||
// configDir contents (marker check, then a walk-up from __dirname), so this
|
||||
// is safe even when configDir has no .msd-source marker (artifactLayout: []).
|
||||
@@ -1468,7 +1339,7 @@ function installRuntimeArtifacts(
|
||||
scope,
|
||||
kinds: executedKinds,
|
||||
cleanup: cleanupResults,
|
||||
postSteps: { hermesBareStemCleanup, nativePlugin: nativePluginInstalled },
|
||||
postSteps: { nativePlugin: nativePluginInstalled },
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1478,7 +1349,7 @@ function installRuntimeArtifacts(
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Install the skills layout kind for an OpenCode-family runtime (OpenCode/Kilo).
|
||||
* Install the skills layout kind for an OpenCode-family runtime (OpenCode).
|
||||
*
|
||||
* These runtimes do NOT go through installRuntimeArtifacts (their commands use a
|
||||
* bespoke flattened-command writer), so this writes ONLY the skills kind
|
||||
@@ -1486,7 +1357,7 @@ function installRuntimeArtifacts(
|
||||
* layout-driven (uninstallRuntimeArtifacts iterates layout.kinds), so the
|
||||
* skills/ dir is cleaned up automatically once the layout declares it.
|
||||
*
|
||||
* @param runtime - 'opencode' or 'kilo'
|
||||
* @param runtime - 'opencode'
|
||||
* @param targetDir - resolved runtime config directory
|
||||
* @param rawCommandsDir - staged RAW Claude command dir (caller's _stageSkills output)
|
||||
* @param pathPrefix - computed config-path prefix for body rewrites
|
||||
@@ -1499,7 +1370,7 @@ function installRuntimeArtifacts(
|
||||
* (capabilityClusters view). When present, installed third-party capability
|
||||
* skills bound to their declaring capId are unioned into the staged output —
|
||||
* the actual #2322 seam (install-profiles.cts stageSkillsForRuntimeAsSkills)
|
||||
* this bespoke OpenCode/Kilo writer never called. Absent -> no third-party
|
||||
* this bespoke OpenCode writer never called. Absent -> no third-party
|
||||
* skills staged (fail closed), matching the seam's own optional-registry
|
||||
* contract.
|
||||
* @returns number of msd-* skill directories written
|
||||
@@ -1518,7 +1389,7 @@ function installOpencodeFamilySkills(
|
||||
if (!skillsKindEntry) return 0;
|
||||
// #3712: combined-family runtimes take installRuntimeArtifacts' early return
|
||||
// BEFORE its guard runs, and this writer honors `skillsKindEntry.home` below and
|
||||
// then prunes that destination. opencode/kilo declare no `home` today, so there
|
||||
// then prunes that destination. opencode declares no `home` today, so there
|
||||
// is no live escape — but that makes this a bypass waiting on a descriptor
|
||||
// change rather than a safe omission, so it is guarded at the writer instead.
|
||||
// Scoped to the SKILLS kind alone, for the same reason as the agents writer.
|
||||
@@ -1528,7 +1399,7 @@ function installOpencodeFamilySkills(
|
||||
|
||||
// #2093: descriptor-driven — dispatch off the skills-kind entry's `converter`
|
||||
// string (capabilities/<runtime>/capability.json artifactLayout) via the
|
||||
// SKILLS_CONVERTER_REGISTRY, instead of a `frontmatterDialect === 'kilo'`
|
||||
// SKILLS_CONVERTER_REGISTRY, instead of a `frontmatterDialect`
|
||||
// runtime check. Fail loud if the descriptor names an unregistered converter
|
||||
// (mirrors the converter=null throw in runtime-artifact-layout.cts).
|
||||
const converterName: string | undefined = skillsKindEntry.converter;
|
||||
@@ -1542,8 +1413,8 @@ function installOpencodeFamilySkills(
|
||||
// #2911: same destination-root defect as _copyStaged/migrateLegacyDevPreferencesToSkill
|
||||
// — honor skillsKindEntry.home as a FALLBACK-preferred override (e.g. Codex skills
|
||||
// -> $HOME/.agents) instead of always resolving against targetDir, so this bespoke
|
||||
// OpenCode/Kilo writer lands in the SAME tree the installer and surface-apply use.
|
||||
// Runtimes with no `home` override (opencode, kilo today) are unaffected. Must stay
|
||||
// OpenCode writer lands in the SAME tree the installer and surface-apply use.
|
||||
// Runtimes with no `home` override (opencode today) are unaffected. Must stay
|
||||
// in lockstep with the sibling writers — the destination-parity test enforces it.
|
||||
const installRoot: string = skillsKindEntry.home ?? targetDir;
|
||||
const dest = runtimeArtifactInstallPlan.assertDestWithinConfigHome(installRoot, skillsKindEntry.destSubpath);
|
||||
@@ -1659,7 +1530,7 @@ function installOpencodeFamilySkills(
|
||||
* combination that never reaches that loop's own `layout.kinds` iteration.
|
||||
* Two such call sites exist (#2875 Part 2):
|
||||
*
|
||||
* 1. **OpenCode-family runtimes** (OpenCode/Kilo, Task A) — `hostBehaviors.
|
||||
* 1. **OpenCode-family runtimes** (OpenCode, Task A) — `hostBehaviors.
|
||||
* combinedFamilyInstall` makes `installRuntimeArtifacts` early-return into
|
||||
* `installOpencodeFamilyArtifacts` instead, which stages commands+skills
|
||||
* via its OWN bespoke writers and never called `resolveRuntimeArtifactLayout`
|
||||
@@ -1679,7 +1550,7 @@ function installOpencodeFamilySkills(
|
||||
* (`layout.kinds` → `agentsKindEntry.stage(resolvedProfile, agentCtx)` →
|
||||
* `_copyStaged`), rather than forking a second agent-staging pipeline. A
|
||||
* runtime/scope whose resolved layout declares no `agents` kind at all
|
||||
* (e.g. pi, whose `artifactLayout` is empty for both scopes) is a no-op
|
||||
* is a no-op
|
||||
* (`null`) — mirrors `installOpencodeFamilySkills`'s own
|
||||
* `if (!skillsKindEntry) return 0` contract.
|
||||
*
|
||||
@@ -1777,14 +1648,14 @@ function installAgentsKindStandalone(
|
||||
|
||||
/**
|
||||
* Install the flattened commands surface for an OpenCode-family runtime
|
||||
* (OpenCode/Kilo): commands/msd/**\/*.md -> command/msd-<...>.md, with
|
||||
* (OpenCode): commands/msd/**\/*.md -> command/msd-<...>.md, with
|
||||
* per-runtime frontmatter conversion and path-prefix/attribution rewrites.
|
||||
*
|
||||
* Mirrors bin/install.js's copyFlattenedCommands VERBATIM (ADR-1239 /
|
||||
* #2087), except attribution is resolved via the injected
|
||||
* `resolveAttribution` callback instead of a module-level getCommitAttribution.
|
||||
*
|
||||
* @param runtime - 'opencode' or 'kilo'
|
||||
* @param runtime - 'opencode'
|
||||
* @param destDir - destination directory for flattened commands (recurses with the same destDir)
|
||||
* @param srcDir - source directory to walk (commands/msd/, recursing into subdirectories)
|
||||
* @param pathPrefix - computed config-path prefix for body rewrites
|
||||
@@ -1821,18 +1692,13 @@ function installOpencodeFamilyCommands(
|
||||
content = applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix);
|
||||
content = processAttribution(content, resolveAttribution(runtime));
|
||||
// #2093: this commands-kind entry's descriptor `converter` field is
|
||||
// intentionally `null` (see capabilities/{kilo,opencode}/capability.json —
|
||||
// intentionally `null` (see capabilities/opencode/capability.json —
|
||||
// the flattened-command writer above applies its own path/attribution
|
||||
// rewrites and has no per-file converter slot to key on), so there is no
|
||||
// descriptor string to dispatch through here. `frontmatterDialect` is the
|
||||
// documented, intentional dispatch key for frontmatter-shape selection —
|
||||
// it is itself descriptor-driven (not a `runtime === 'kilo'` check), so it
|
||||
// already satisfies the fold-to-descriptor requirement. Only the SKILLS
|
||||
// converter site above (installOpencodeFamilySkills) has a real
|
||||
// `converter` string to key on via SKILLS_CONVERTER_REGISTRY.
|
||||
content = _hostBehaviors(runtime).frontmatterDialect === 'kilo'
|
||||
? (runtimeArtifactConversion as any).convertClaudeToKiloFrontmatter(content)
|
||||
: (runtimeArtifactConversion as any).convertClaudeToOpencodeFrontmatter(content);
|
||||
// descriptor string to dispatch through here. Only the SKILLS converter
|
||||
// site above (installOpencodeFamilySkills) has a real `converter` string
|
||||
// to key on via SKILLS_CONVERTER_REGISTRY.
|
||||
content = (runtimeArtifactConversion as any).convertClaudeToOpencodeFrontmatter(content);
|
||||
installFs().writeFileSync(path.join(destDir, destName), content);
|
||||
}
|
||||
}
|
||||
@@ -1848,10 +1714,9 @@ function installOpencodeFamilyCommands(
|
||||
*
|
||||
* Extracted (ADR-1239 / #2102 Stage 1) from the body previously inlined in
|
||||
* installOpencodeFamilyArtifacts so a runtime that is NOT part of the
|
||||
* OpenCode/Kilo combined-family install (e.g. pi, whose artifactLayout is
|
||||
* empty and which never sets combinedFamilyInstall) can still get its
|
||||
* nativePlugin file staged via the generic installRuntimeArtifacts branch.
|
||||
* Behavior for opencode/kilo is unchanged — same source resolution, same
|
||||
* OpenCode combined-family install can still get its nativePlugin file
|
||||
* staged via the generic installRuntimeArtifacts branch.
|
||||
* Behavior for opencode is unchanged — same source resolution, same
|
||||
* mkdir + copyFileSync call, same silent no-op when the source is missing.
|
||||
*
|
||||
* @param runtime - canonical runtime id (only used for the assertDestWithinConfigHome guard)
|
||||
@@ -1888,9 +1753,8 @@ function _installNativePluginIfDeclared(
|
||||
// marker the installer wrote at the config root — the write that
|
||||
// clobbered user-authored files. Pin it from the plugin's own directory
|
||||
// instead, leaving the config root alone. The marker cannot disturb
|
||||
// plugin discovery: OpenCode auto-discovers `plugins/*.{ts,js}` and pi's
|
||||
// isExtensionFile() accepts only `.ts`/`.js` (see installer-migration
|
||||
// 006), so a package.json here is never treated as a plugin. Never
|
||||
// plugin discovery: OpenCode auto-discovers `plugins/*.{ts,js}`, so a
|
||||
// package.json here is never treated as a plugin. Never
|
||||
// written over a package.json MSD does not own — but when one is already
|
||||
// there, say so: the adapter is CommonJS and will not load under a
|
||||
// foreign `"type": "module"`, and a silent no-op would leave every guard
|
||||
@@ -1922,8 +1786,8 @@ function _installNativePluginIfDeclared(
|
||||
* #2329: migrate a pre-fix OpenCode install's legacy singular `command/`
|
||||
* command directory into the current descriptor-driven destination (plural
|
||||
* `commands/` for OpenCode — the dir OpenCode actually discovers slash
|
||||
* commands from; unaffected for Kilo, whose descriptor still declares
|
||||
* `command`, so `currentName === LEGACY_NAME` short-circuits below).
|
||||
* commands from; a descriptor that still declares `command` short-circuits
|
||||
* below via `currentName === LEGACY_NAME`).
|
||||
*
|
||||
* Runs BEFORE materialization writes the fresh command set to the new
|
||||
* location (mirroring `_runLegacyInstallMigrations`'s ordering for the
|
||||
@@ -1946,14 +1810,14 @@ function _installNativePluginIfDeclared(
|
||||
* Types), so the empty-directory removal below would need this same
|
||||
* hand-written glue regardless. It also intentionally is NOT reachable via
|
||||
* combinedFamilyInstall's early return above `_runLegacyInstallMigrations`,
|
||||
* matching the existing precedent that OpenCode/Kilo's bespoke install path
|
||||
* matching the existing precedent that OpenCode's bespoke install path
|
||||
* owns its own legacy cleanup rather than routing through the generic
|
||||
* layout-driven migrations hook.
|
||||
*/
|
||||
function _migrateLegacyOpencodeCommandDir(runtime: string, configDir: string, behaviors: any): void {
|
||||
const LEGACY_NAME = 'command';
|
||||
const currentName = behaviors.flatCommandDir || LEGACY_NAME;
|
||||
if (currentName === LEGACY_NAME) return; // e.g. Kilo — legacy IS the current location; nothing to migrate
|
||||
if (currentName === LEGACY_NAME) return; // legacy IS the current location; nothing to migrate
|
||||
const legacyDir = path.join(configDir, LEGACY_NAME);
|
||||
if (!installFs().existsSync(legacyDir)) return;
|
||||
// Never follow a symlinked legacy dir out of configDir.
|
||||
@@ -1998,13 +1862,13 @@ function _migrateLegacyOpencodeCommandDir(runtime: string, configDir: string, be
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Combined-family install orchestrator for OpenCode/Kilo (ADR-1239 / #2087,
|
||||
* Combined-family install orchestrator for OpenCode (ADR-1239 / #2087,
|
||||
* #2093). Stages the flattened commands surface + skills surface + (any
|
||||
* runtime whose hostBehaviors declares `nativePlugin` — OpenCode and, since
|
||||
* #2093, Kilo) native plugin adapter, mirroring the bespoke `else if (isOpencode ||
|
||||
* isKilo)` block previously inlined in bin/install.js.
|
||||
* runtime whose hostBehaviors declares `nativePlugin`) native plugin adapter,
|
||||
* mirroring the bespoke `else if (isOpencode)` block previously inlined in
|
||||
* bin/install.js.
|
||||
*
|
||||
* @param runtime - 'opencode' or 'kilo'
|
||||
* @param runtime - 'opencode'
|
||||
* @param configDir - resolved runtime config directory
|
||||
* @param scope - install scope ('global' | 'local')
|
||||
* @param resolvedProfile - from resolveProfile() / resolveEffectiveProfile()
|
||||
@@ -2013,7 +1877,7 @@ function _migrateLegacyOpencodeCommandDir(runtime: string, configDir: string, be
|
||||
* @param capabilityRegistry - #2362: optional composed capability registry
|
||||
* (capabilityClusters view), threaded straight through to
|
||||
* installOpencodeFamilySkills so an installed third-party capability skill
|
||||
* materializes for this combined-family (OpenCode/Kilo) install path too.
|
||||
* materializes for this combined-family (OpenCode) install path too.
|
||||
* Absent -> no third-party skills staged (fail closed).
|
||||
* @param projectDir - project/config discovery root, distinct from configDir for global installs
|
||||
* @returns #2874 design row 2: an executed-plan value, same top-level shape
|
||||
@@ -2063,8 +1927,7 @@ function installOpencodeFamilyArtifacts(
|
||||
// resolveRuntimeArtifactLayout's commands-kind destSubpath — a hardcoded
|
||||
// literal here would silently diverge from the descriptor the moment either
|
||||
// is edited (Generative Fix Divergence guard). OpenCode uses 'commands'
|
||||
// (plural, the dir OpenCode actually discovers slash commands from); Kilo
|
||||
// keeps its own descriptor value ('command', singular) unchanged.
|
||||
// (plural, the dir OpenCode actually discovers slash commands from).
|
||||
const commandDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(
|
||||
configDir,
|
||||
behaviors.flatCommandDir || 'command',
|
||||
@@ -2093,7 +1956,7 @@ function installOpencodeFamilyArtifacts(
|
||||
...(agentsResult ? [{ kind: 'agents', sourceDir: agentsResult.sourceDir, destDir: agentsResult.destDir }] : []),
|
||||
],
|
||||
cleanup: [],
|
||||
postSteps: { hermesBareStemCleanup: false, nativePlugin: Boolean(behaviors.nativePlugin) },
|
||||
postSteps: { nativePlugin: Boolean(behaviors.nativePlugin) },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2145,23 +2008,6 @@ function uninstallRuntimeArtifacts(
|
||||
_removeMsdEntries(item.destDir, kind);
|
||||
}
|
||||
|
||||
// Hermes: after removing msd-* skill dirs from skills/msd/, also remove
|
||||
// the MSD-managed DESCRIPTION.md and then the category dir itself if it
|
||||
// contains no user content (#947). _removeMsdEntries removed msd-* dirs
|
||||
// but left the category container and DESCRIPTION.md intact.
|
||||
if (runtime === 'hermes') {
|
||||
const nestedMsdDir = path.join(configDir, 'skills', 'msd');
|
||||
if (fs.existsSync(nestedMsdDir)) {
|
||||
// Remove MSD-owned DESCRIPTION.md (written by writeHermesCategoryDescription)
|
||||
fs.rmSync(path.join(nestedMsdDir, 'DESCRIPTION.md'), { force: true });
|
||||
// Remove the category dir if empty (no user content remaining)
|
||||
const remaining = fs.readdirSync(nestedMsdDir, { withFileTypes: true });
|
||||
if (remaining.length === 0) {
|
||||
fs.rmSync(nestedMsdDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #2973 / Codex review (bd1f06c9): migrate dev-preferences.md to the
|
||||
// runtime-aware SKILL.md location after all layout-driven removal is
|
||||
// complete. Do NOT restore to commands/msd/ — the user is uninstalling.
|
||||
@@ -2221,12 +2067,10 @@ export = {
|
||||
migrateLegacyDevPreferencesToSkill,
|
||||
applyOpencodeFamilyPathPrefix,
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
USER_OWNED_ARTIFACTS,
|
||||
_runLegacyInstallMigrations,
|
||||
_runLegacyUninstallCleanup,
|
||||
_removeMsdEntries,
|
||||
_snapshotDir,
|
||||
_restoreDir,
|
||||
_removeHermesBareStemDirs,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user