docs/adr/0010-skill-surface-budget-module.md
We propose extending the existing install profile seam (get-shit-done/bin/lib/install-profiles.cjs) into a Skill Surface Budget Module that owns which subset of GSD's 66 skills is written to the runtime config dirs, and that owns the per-skill requires: dependency manifest used to keep that subset closed under cross-skill references. GSD currently ships a binary --minimal / full toggle; runtimes that enumerate skills (Claude Code, OpenCode, etc.) cap the <available_skills> system-prompt block at skillListingBudgetFraction of the context window (default 1% = ~2k tokens at 200k), and GSD alone consumes ~60% of that cap (#3408). Further description shrinkage is unavailable — scripts/lint-descriptions.cjs already enforces a hard 100-char ceiling and the mean is 72.5 chars. The remaining lever is surfacing fewer skills, which requires a typed profile model plus a dependency manifest, not more ad-hoc allowlists.
get-shit-done/bin/lib/install-profiles.cjs as the single owner for which commands/gsd/*.md and agents/gsd-*.md files are staged into the per-runtime copy pipeline.MINIMAL_SKILL_ALLOWLIST constant with a typed PROFILES map keyed by profile name. Each profile is a base set of skills; the module computes the transitive closure over each skill's declared requires: set before staging.requires: frontmatter field to every skill whose body references another GSD skill. The dependency graph in the research memo (docs/research/2026-05-12-skill-surface-budget.md §3.1) is the migration spec for this pass.bin/install.js argument parsing to accept --profile=<name> and --profile=<name1>,<name2> (composable). Preserve --minimal / --core-only as aliases for --profile=core. Default install (no flag) remains full for back-compat.~/.claude/skills/.gsd-profile (and runtime-equivalent locations) so gsd update re-applies the same profile instead of expanding silently to full.scripts/lint-skill-deps.cjs and wire it into the existing npm run lint:descriptions pretest gate. The lint fails if:
requires: set, orrequires: closure is not satisfied.AskUserQuestion-style flow already used for runtime/location selection. Non-interactive installs (CI, npx --yes) fall back to --profile=full unless overridden.First migration slice should land the profile model and one new tier above core:
core (current minimal, 7 skills including phase), standard (~13 skills covering the audit + main-loop + utility floor), full (current default, 66 skills).requires: frontmatter added to the hot nodes of the dependency graph first: phase (38 callers), review (11), config (7), progress (5), update (5). These are the skills whose absence silently breaks others, so they need explicit required_by audit before any profile narrows them out.phase is referenced by 38 skills and now belongs in MINIMAL_SKILL_ALLOWLIST / PROFILES.core. Keep explicit coverage in minimal/core tests so this cannot regress.--profile=, comma-composed profiles, --profile=help listing each profile's contents and token cost.gsd update re-application.It should not in the first pass:
/gsd:surface). Track as a follow-up ADR (see "Open questions").*-phase into a dispatcher). Track separately as IA cleanup; orthogonal to surface curation.get-shit-done/bin/lib/install-profiles.cjsMINIMAL_SKILL_ALLOWLIST Object.freeze constant with PROFILES Object.freeze map of profile-name → base skill set.isMinimalMode(mode) with resolveProfile(mode) returning a typed {name, skills: Set, agents: Set} after transitive-closure computation.shouldInstallSkill(name, mode) with shouldInstallSkill(name, resolvedProfile).stageSkillsForMode(srcDir, mode) with stageSkillsForProfile(srcDir, resolvedProfile). Add a sibling stageAgentsForProfile since this module now owns agent staging too (current --minimal skips agents wholesale; tiered profiles need finer control).STAGED_DIRS, ensureExitCleanup) unchanged — the bug surface it covers is the same.bin/install.jsThese call sites should migrate behind the Skill Surface Budget Module:
--minimal / --core-only flag parsing — bin/install.js:123-124_effectiveInstallMode plumbing + isMinimalMode() checks — bin/install.js:7634-8465 (passes through to per-runtime copy fns)bin/install.js:8167-8207 (becomes "skip agents not in profile")stageSkillsForMode — 13 sites per the existing comment in install-profiles.cjsbin/install.js:508 (add --profile= documentation)requires: field to every skill in commands/gsd/*.md whose body references another GSD skill. Audit data lists the full set (docs/research/2026-05-12-skill-surface-budget.md §3.1). Estimate: 25-30 files touched in Phase 1.lint-skill-deps.cjs enforces consistency, not presence.scripts/lint-skill-deps.cjscommands/gsd/*.md, parses requires:, walks the body for gsd:<name> or \b<stem>\b references to other skills (same matching rules documented in docs/research/2026-05-12-skill-surface-budget.md §3.1).requires: set ≠ actual references (modulo ignore-list for prose mentions that aren't actual dispatches).PROFILES from install-profiles.cjs, fails if any profile's transitive closure references a skill not in the profile.npm run lint:descriptions (or as a sibling lint:skill-deps) and pretest.~/.claude/skills/.gsd-profile (and per-runtime equivalents under .codex/, .cursor/, etc. as enumerated in install.js) containing the active profile name.core exactly, write core; otherwise write full. Migrations are idempotent per existing module contract.tests/install-profiles-*.test.cjs (any existing) — extend to assert profile resolution, transitive closure, and --profile=core,standard composition.tests/skill-surface-budget-*.test.cjs covering:
discuss-phase must transitively include phase if discuss-phase requires itlint:skill-deps failgsd install --profile=standard followed by gsd update preserves standard--minimal resolves to --profile=core and emits the same file set as today (modulo the phase-inclusion bug fix)The module should accept typed profile intent and return a typed resolved profile:
// install-profiles.cjs (extended)
resolveProfile({
modes: ['core' | 'standard' | 'full'],
skillsManifest: ManifestMap, // parsed `requires:` graph
})
// → { name: 'standard', skills: Set<string>, agents: Set<string> }
Profile composition: --profile=core,standard resolves to union(closure(core), closure(standard)). --profile=full is the identity profile (every skill).
stageSkillsForProfile(srcDir, resolvedProfile) // returns staged dir path
stageAgentsForProfile(srcAgentsDir, resolvedProfile) // new
Profile marker IO is typed too, not stringly:
readActiveProfile(runtimeConfigDir) // → 'core' | 'standard' | 'full' | null
writeActiveProfile(runtimeConfigDir, profileName)
Per-skill frontmatter contract:
---
name: gsd:plan-phase
description: ...
requires: [phase, discuss-phase] # GSD skills only; not Claude Code primitives
---
requires: lists GSD skills (file stems). It does not include Claude Code built-ins (Read, Bash, etc.) — those continue to live in allowed-tools: per existing convention.
gsd update stops silently re-expanding a --minimal install to full — a current foot-gun documented inline in install-profiles.cjs (its module-level comment recommends gsd update without --minimal to "expand to the full surface"; that path remains available, but the default gsd update now respects the recorded profile).requires: manifest creates a new authoring obligation (~30 files in Phase 1), enforced by CI. Skill authors who add a /gsd:phase reference in a new skill body have to update requires:. The lint script keeps drift low-cost.phase-in-minimal latent gap (research memo §3.1) gets resolved as a side effect of adopting closure-based profile resolution — phase is auto-included whenever any minimal-loop skill requires: it.full for non-interactive (npx --yes) installs, so back-compat for CI scripts is preserved..disabled toggles — see Open Questions). It does not, in this ADR, use those features.CONTEXT.md should gain a canonical Skill Surface Budget Module entry alongside the existing seam entries, and future architecture reviews should treat ad-hoc commands/gsd/ filtering outside this seam as drift./gsd:surface command (research memo §4 Option B) should be its own ADR or an amendment to this one. Leaning separate ADR because it introduces persistent runtime state outside the install pipeline.core / standard / full is the working proposal. Alternatives surveyed: minimal / recommended / everything, functional names (planning, audit, research). Settle in the implementation PR after a contributor poll.requires: field should also be consumed by /gsd:help to render a "skills you have installed and what depends on what" graph. Likely yes, but out of scope for this ADR.phase explicitly listed in core forever vs relying purely on closure semantics. Current recommendation: keep explicit listing because minimal mode has a back-compat allowlist path.standard profile line goes. Without it, the cut points are author-intuition. Track separately; not a blocker..disabled toggles) should be filed before or after this ADR ships. Recommendation: file as a feedback bundle when ADR is accepted, so we ship Phase 1 unilaterally and platform improvements compose on top.#3408docs/research/2026-05-12-skill-surface-budget.mdget-shit-done/bin/lib/install-profiles.cjsscripts/lint-descriptions.cjsbin/install.js:123-124, :8167-82070008-installer-migration-module.md (the migration that records the profile marker lives here)0005-sdk-architecture-seam-map.md (the seam map this module joins)