fix(skills): defer greenfield skill selection to post-design phase

Greenfield projects (empty directories) no longer ask users to pick a tech
stack during init. Non-technical users may not know their architecture yet.
Instead, only essential packs (find-skills, skill-creator, agent-browser,
document handling) are installed. Stack-specific skills auto-detect later
via brownfield detection once the LLM creates project files.

GREENFIELD_STACKS retained as a reference for programmatic use by the LLM
and the /gsd skills command.
This commit is contained in:
Derek Pearson 2026-03-22 06:11:44 -04:00
parent ddeb352143
commit aa0822466e

View file

@ -297,8 +297,13 @@ export const SKILL_CATALOG: SkillPack[] = [
// ─── Greenfield Tech Stack Choices ────────────────────────────────────────────
/**
* Choices shown to users when no tech stack can be auto-detected
* (greenfield repos or empty directories).
* Tech stack pack mappings for programmatic use.
*
* NOT shown directly to users during init (greenfield installs essentials
* only and defers stack-specific skills). These mappings are available for:
* 1. The LLM to install skills after establishing a design
* 2. The `/gsd skills` command (explicit user request)
* 3. Re-running brownfield detection after project files are created
*/
export const GREENFIELD_STACKS: Array<{
id: string;
@ -513,7 +518,11 @@ export function isPackInstalled(pack: SkillPack): boolean {
* Auto-detects tech stack shows matched packs installs accepted ones.
*
* Greenfield (no files detected):
* Asks user what tech stack they're using maps to packs installs.
* Installs essential packs only (find-skills, skill-creator, etc.).
* Stack-specific skills are deferred once the LLM establishes a design
* and creates project files (package.json, firebase.json, etc.), brownfield
* detection will pick them up on the next `gsd init` or via auto-mode
* skill discovery.
*
* Returns the list of installed pack labels.
*/
@ -590,37 +599,45 @@ export async function runSkillInstallStep(
}
}
} else {
// ── Greenfield: ask user what they're building ──────────────────────────
const stackChoice = await showNextAction(ctx, {
title: "GSD — Project Skills",
// ── Greenfield: install essentials only ─────────────────────────────────
// Don't ask the user what tech stack they're building — they may not know
// yet, especially non-technical users. Install essential packs (discovery,
// authoring, browser, docs) and let stack-specific skills auto-detect later
// once the LLM establishes the design and creates project files.
const essentials = SKILL_CATALOG.filter((p) => p.matchAlways && !isPackInstalled(p));
if (essentials.length === 0) return installed;
const totalSkills = essentials.reduce((n, p) => n + p.skills.length, 0);
const choice = await showNextAction(ctx, {
title: "GSD — Install Essential Skills",
summary: [
"What are you building? GSD will install relevant agent skills.",
"Skills are installed globally via skills.sh and shared across agents.",
"GSD will install essential agent skills (skill discovery, authoring,",
"browser automation, document handling).",
"",
"Stack-specific skills (React, Swift, Python, etc.) will be recommended",
"automatically once your project files are in place.",
],
actions: [
{
id: "install",
label: "Install essentials",
description: `Install ${totalSkills} essential skills via skills.sh`,
recommended: true,
},
{
id: "skip",
label: "Skip",
description: "Install skills later with npx skills add",
},
],
actions: GREENFIELD_STACKS.map((s) => ({
id: s.id,
label: s.label,
description: s.description,
})),
notYetMessage: "Run /gsd init when ready.",
});
if (stackChoice === "not_yet" || stackChoice === "other") return installed;
const stack = GREENFIELD_STACKS.find((s) => s.id === stackChoice);
if (!stack) return installed;
const packsToInstall = SKILL_CATALOG.filter((p) =>
stack.packs.includes(p.label),
).filter((p) => !isPackInstalled(p));
const labels = await installPacksBatched(packsToInstall, (label) => {
ctx.ui.notify(`Installing ${label} skills...`, "info");
});
installed.push(...labels);
const failed = packsToInstall.filter((p) => !installed.includes(p.label));
for (const pack of failed) {
ctx.ui.notify(`Failed to install ${pack.label} — try manually: npx skills add ${pack.repo}`, "info");
if (choice === "install") {
const labels = await installPacksBatched(essentials, (label) => {
ctx.ui.notify(`Installing ${label} skills...`, "info");
});
installed.push(...labels);
}
}