From d6cf377064b8297fc68542c33087c58bde4c81ee Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 6 Jul 2026 07:48:28 -0400 Subject: [PATCH] fix(#1925): correct _isSkillsRuntime discriminator + zcode install contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The descriptor-driven gate must treat a runtime as layout-driven when its scoped artifactLayout is non-empty (any kind), not only when it has a skills kind — windsurf's global layout is agents-only and is a legitimate layout-driven runtime. Preserve the three legacy special-cased paths (opencode/kilo combined path, claude-local copyWithPathReplacement). zcode falls into the default package.json+hooks path (not in the CommonJS-mode exclusion roster), so its install contract is packageJson: true — matching observed install output. No runtime === 'zcode' branch is added (AC#3): zcode gets the default by not being excluded. --- bin/install.js | 17 ++++++++++------- ...aller-migration-install.integration.test.cjs | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bin/install.js b/bin/install.js index d2588bd6a..1159443e9 100755 --- a/bin/install.js +++ b/bin/install.js @@ -8595,19 +8595,22 @@ function install(isGlobal, runtime = 'claude', options = {}) { // handles per-runtime path + branding rewrites, including Qwen/Hermes. // Cline global: emit skills to ~/.cline/skills/ (Cline >= v3.48.0 — #782). // Descriptor-driven (ADR-1016 / ADR-1239): a runtime takes the layout-driven - // skills-install path when its scoped artifactLayout declares a skills kind. + // installRuntimeArtifacts path when its scoped artifactLayout is non-empty + // (it declares any skills/commands/agents/kimi-agents kind for this scope). // This replaces the prior hardcoded `isCodex || isCopilot || ...` roster so a - // newly-added runtime with a skills layout installs without a per-runtime - // branch — the add-a-host tax ADR-1239 Phase B retires. opencode/kilo keep - // their specialized combined commands+skills path (copyFlattenedCommands + - // installOpencodeFamilySkills) below. + // newly-added runtime with an artifact layout installs without a per-runtime + // branch — the add-a-host tax ADR-1239 Phase B retires. Three legacy + // special-cased paths are preserved: opencode/kilo (combined commands+skills + // via copyFlattenedCommands + installOpencodeFamilySkills) and claude-local + // (copyWithPathReplacement + stale-skills cleanup). const _isSkillsRuntime = (() => { - if (isOpencode || isKilo) return false; + if (isOpencode || isKilo) return false; // specialized combined path + if (runtime === 'claude' && !isGlobal) return false; // claude-local legacy path const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime]; const layout = cap && cap.runtime && cap.runtime.artifactLayout; if (!layout) return false; const scopeLayout = isGlobal ? layout.global : layout.local; - return Array.isArray(scopeLayout) && scopeLayout.some((k) => k && k.kind === 'skills'); + return Array.isArray(scopeLayout) && scopeLayout.length > 0; })(); if (_isSkillsRuntime) { diff --git a/tests/installer-migration-install.integration.test.cjs b/tests/installer-migration-install.integration.test.cjs index 11c3543f1..92b3dafd2 100644 --- a/tests/installer-migration-install.integration.test.cjs +++ b/tests/installer-migration-install.integration.test.cjs @@ -40,7 +40,7 @@ const RUNTIME_INSTALL_CONTRACTS = { qwen: { surface: 'flat-skills', settings: true, packageJson: true }, trae: { surface: 'flat-skills', settings: false, packageJson: false }, windsurf: { surface: 'global-artifacts-noop', settings: false, packageJson: false }, - zcode: { surface: 'flat-skills', settings: false, packageJson: false }, + zcode: { surface: 'flat-skills', settings: false, packageJson: true }, }; function sha256(content) {