| import { getSettingsForSource } from './settings.js' | |
| import type { CUSTOMIZATION_SURFACES } from './types.js' | |
| export type CustomizationSurface = (typeof CUSTOMIZATION_SURFACES)[number] | |
| /** | |
| * Check whether a customization surface is locked to plugin-only sources | |
| * by the managed `strictPluginOnlyCustomization` policy. | |
| * | |
| * "Locked" means user-level (~/.claude/*) and project-level (.claude/*) | |
| * sources are skipped for that surface. Managed (policySettings) and | |
| * plugin-provided sources always load regardless β the policy is admin-set, | |
| * so managed sources are already admin-controlled, and plugins are gated | |
| * separately via `strictKnownMarketplaces`. | |
| * | |
| * `true` locks all four surfaces; array form locks only those listed. | |
| * Absent/undefined β nothing locked (the default). | |
| */ | |
| export function isRestrictedToPluginOnly( | |
| surface: CustomizationSurface, | |
| ): boolean { | |
| const policy = | |
| getSettingsForSource('policySettings')?.strictPluginOnlyCustomization | |
| if (policy === true) return true | |
| if (Array.isArray(policy)) return policy.includes(surface) | |
| return false | |
| } | |
| /** | |
| * Sources that bypass strictPluginOnlyCustomization. Admin-trusted because: | |
| * plugin β gated separately by strictKnownMarketplaces | |
| * policySettings β from managed settings, admin-controlled by definition | |
| * built-in / builtin / bundled β ship with the CLI, not user-authored | |
| * | |
| * Everything else (userSettings, projectSettings, localSettings, flagSettings, | |
| * mcp, undefined) is user-controlled and blocked when the relevant surface | |
| * is locked. Covers both AgentDefinition.source ('built-in' with hyphen) and | |
| * Command.source ('builtin' no hyphen, plus 'bundled'). | |
| */ | |
| const ADMIN_TRUSTED_SOURCES: ReadonlySet<string> = new Set([ | |
| 'plugin', | |
| 'policySettings', | |
| 'built-in', | |
| 'builtin', | |
| 'bundled', | |
| ]) | |
| /** | |
| * Whether a customization's source is admin-trusted under | |
| * strictPluginOnlyCustomization. Use this to gate frontmatter-hook | |
| * registration and similar per-item checks where the item carries a | |
| * source tag but the surface's filesystem loader already ran. | |
| * | |
| * Pattern at call sites: | |
| * const allowed = !isRestrictedToPluginOnly(surface) || isSourceAdminTrusted(item.source) | |
| * if (item.hooks && allowed) { register(...) } | |
| */ | |
| export function isSourceAdminTrusted(source: string | undefined): boolean { | |
| return source !== undefined && ADMIN_TRUSTED_SOURCES.has(source) | |
| } | |