feat(#949): install/surface consume derived profiles/clusters (ADR-857 phase 4c) (#954)

Make install + surface read the registry's derived profileMembership/
capabilityClusters so a capability's tier drives what installs + surfaces.
resolveProfile (when given the registry) unions capability skills for the
profiles its tier implies before the requires: closure; resolveSurface merges
capabilityClusters into the cluster map. bin/install.js, /gsd:surface, and the
capability-state resolver all thread the registry.

Shipped as a proven no-op: the UI capability is reconciled to tier:full (its
skills were full-only in the hand-authored profiles), so it contributes only to
the full profile (already the '*' sentinel) and core/standard are unchanged.
Equivalence tests prove resolveProfile/resolveSurface/listSurface/staging/
capability-state are identical with vs without the registry; the core-alias
staging path is verified equivalent (empty manifest → raw PROFILES.core).

Closes #949

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-09 22:35:42 -04:00
committed by GitHub
parent e006ff74fd
commit dc139b38e0
9 changed files with 982 additions and 80 deletions

View File

@@ -324,6 +324,13 @@ const {
stageAgentsForProfile,
stageSkillsForRuntimeAsSkills,
} = require(path.join(_gsdLibDir, 'install-profiles.cjs'));
// ADR-857 phase 4c: load capability registry (optional; missing → falls back to undefined)
let _capabilityRegistry;
try {
_capabilityRegistry = require(path.join(_gsdLibDir, 'capability-registry.cjs'));
} catch (_) {
_capabilityRegistry = undefined;
}
const {
applyInstallerMigrationPlan,
discoverInstallerMigrations,
@@ -10057,7 +10064,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
// @-references resolve correctly (#2376 Windows, #2831 macOS/Linux).
// gsd update marker re-application (ADR-0010 Deviation 2):
// Resolve which profile to use for this runtime's install:
// 1. --minimal / --core-only → back-compat path (stageSkillsForMode keeps strict core allowlist)
// 1. --minimal / --core-only → back-compat alias for the core profile
// 2. Explicit --profile=<name> → use it (overrides any marker)
// 3. Marker exists in targetDir → honor it (prevents silent expansion on update)
// 4. Else → 'full' (back-compat for fresh non-interactive installs)
@@ -10066,8 +10073,16 @@ function install(isGlobal, runtime = 'claude', options = {}) {
// differ, the caller may use mostRestrictiveProfile() across the per-runtime
// results — here we resolve each runtime independently.
//
// Note: --minimal uses stageSkillsForMode (back-compat: strict allowlist, no closure).
// Named profiles (--profile=X or marker-driven) use resolveProfile() for transitive closure.
// ADR-857 phase 4c: ALL profiles (including core/minimal) use stageSkillsForProfile
// with the registry-aware _resolvedProfile so future tier:core capabilities are
// staged on core installs. The 'minimal' back-compat distinction is now ONLY the
// empty manifest (core profile has no transitive deps); the registry IS consulted.
// MINIMAL is intentionally the same skill set as the 'core' profile
// (MINIMAL_ALLOWLIST_SET === Set(PROFILES.core)) — it is NOT a separately curated
// subset. Any future tier:core capability therefore DOES belong in a minimal/core
// install. Using stageSkillsForProfile(_resolvedProfile) honors the registry while
// keeping the effective skill set identical to the prior stageSkillsForMode path
// until a tier:core capability is registered.
const _activeProfileName = hasMinimal
? 'core' // --minimal is a back-compat alias for the core profile; marker records 'core'
: resolveEffectiveProfile({
@@ -10078,19 +10093,18 @@ function install(isGlobal, runtime = 'claude', options = {}) {
const _effectiveInstallMode = _isCoreProfileAlias ? 'minimal' : 'full';
// Load the manifest and compute resolved profile for named profiles.
// For --minimal/core: use an empty manifest (core profile has no transitive
// deps) to produce a resolvedProfile with the core skill set. This allows
// installRuntimeArtifacts to use stageSkillsForProfile uniformly across all
// profile modes without a null sentinel.
// deps) to produce a resolvedProfile with the core skill set. Registry IS
// consulted so tier:core capability skills are included when registered.
const _commandsDir = path.join(src, 'commands', 'gsd');
const _skillsManifest = _isCoreProfileAlias ? new Map() : loadSkillsManifest(_commandsDir);
const _resolvedProfile = resolveProfile({
modes: [_activeProfileName],
manifest: _skillsManifest,
registry: _capabilityRegistry,
});
// Unified staging function: for --minimal uses stageSkillsForMode (back-compat);
// for named profiles uses stageSkillsForProfile (new API with transitive closure).
// Unified staging function: all profiles use stageSkillsForProfile with the
// registry-aware _resolvedProfile (ADR-857 phase 4c cutover).
function _stageSkills(commandsGsdDir) {
if (_isCoreProfileAlias) return stageSkillsForMode(commandsGsdDir, _effectiveInstallMode);
return stageSkillsForProfile(commandsGsdDir, _resolvedProfile);
}
function _stageAgents(agentsDir) {